////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 2010.
// -------------------------------------------------------------------------
//  File name:   CryGameSpyMatchMaking.cpp
//  Version:     v1.00
//  Created:     29/03/2010 by Paul Mikell.
//  Description: CCryGameSpyMatchMaking member definitions
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////


#include "StdAfx.h"
#include "ICryLobby.h"
#include "CryGameSpyLobby.h"
#include "CryGameSpyMatchMaking.h"
#include "Lobby/CrySharedLobbyPacket.h"
#include "Network.h"
#include "TypeInfo_impl.h"

#if USE_CRYLOBBY_GAMESPY

#include "Gamespy2.11.00/Source/gcdkey/gcdkeyc.h"
#include "Gamespy2.11.00/Source/gcdkey/gcdkeys.h"
#include "IGame.h"


const char* const		CCryGameSpyMatchMaking::k_gameModeNames[ eGM_NumGameModes ] =
{
	"openwaiting",
	"closedwaiting",
	"closedplaying",
	"openplaying",
	"openstaging",
	"closedstaging",
	"exiting"
};

const char* const		CCryGameSpyMatchMaking::k_sessionIDKeyName = "si";
const char* const		CCryGameSpyMatchMaking::k_numPrivateSlotsKeyName = "is";
const char* const		CCryGameSpyMatchMaking::k_numPublicSlotsKeyName = "ps";
const char* const		CCryGameSpyMatchMaking::k_rankedKeyName = "r";
const char* const		CCryGameSpyMatchMaking::k_createFlagsKeyName = "f";
const char* const		CCryGameSpyMatchMaking::k_numMigrationsKeyName = "m";
const char* const		CCryGameSpyMatchMaking::k_passwordRequiredKeyName = "pw";
const char* const		CCryGameSpyMatchMaking::k_dedicatedKeyName = "ds";

enum ECryLobbyUserDataTypeOperation
{
	eCLUDTO_Int64Equal = eCLUDT_Int64 | ( eCSSO_Equal << 16 ),
	eCLUDTO_Int64NotEqual = eCLUDT_Int64 | ( eCSSO_NotEqual << 16 ),
	eCLUDTO_Int64LessThan = eCLUDT_Int64 | ( eCSSO_LessThan << 16 ),
	eCLUDTO_Int64LessThanEqual = eCLUDT_Int64 | ( eCSSO_LessThanEqual << 16 ),
	eCLUDTO_Int64GreaterThan = eCLUDT_Int64 | ( eCSSO_GreaterThan << 16 ),
	eCLUDTO_Int64GreaterThanEqual = eCLUDT_Int64 | ( eCSSO_GreaterThanEqual << 16 ),
	eCLUDTO_Int64BitwiseAndNotEqualZero = eCLUDT_Int64 | ( eCSSO_BitwiseAndNotEqualZero << 16 ),
	eCLUDTO_Float64Equal = eCLUDT_Float64 | ( eCSSO_Equal << 16 ),
	eCLUDTO_Float64NotEqual = eCLUDT_Float64 | ( eCSSO_NotEqual << 16 ),
	eCLUDTO_Float64LessThan = eCLUDT_Float64 | ( eCSSO_LessThan << 16 ),
	eCLUDTO_Float64LessThanEqual = eCLUDT_Float64 | ( eCSSO_LessThanEqual << 16 ),
	eCLUDTO_Float64GreaterThan = eCLUDT_Float64 | ( eCSSO_GreaterThan << 16 ),
	eCLUDTO_Float64GreaterThanEqual = eCLUDT_Float64 | ( eCSSO_GreaterThanEqual << 16 ),
};

#define KEY_USERDATA_IDX_INTERNAL	254
#define KEY_USERDATA_IDX_UNUSED		255

#define JOIN_PARAM													0
#define SEARCH_PARAM_GAME_PARAM							0	// ptr
#define SEARCH_PARAM_GAME_PARAM_DATA				1	// ptr
#define MIGRATE_SERVER_PARAM								0
#define MIGRATE_CLIENT_PARAM								0
#define ENSURE_CORRECT_STAGING_ROOM_PARAM		0
#define QUERY_PARAM													0
#define CREATE_CONTROL_PARAM								0
#define CREATE_SESSION_DATA_PARAM						1
#define CREATE_SESSION_USER_DATA_PARAM			2
#define UPDATE_SESSION_DATA_PARAM						0
#define UPDATE_SESSION_USER_DATA_PARAM			1

#define TDM_SEND_REQUEST_INFO_SESSIONID							0
#define TDN_SEND_REQUEST_INFO_COOKIE								0
#define TDM_SEND_REQUEST_INFO_PACKET								1
#define TDN_SEND_REQUEST_INFO_PACKET_SIZE						1
#define TDM_SEND_REQUEST_INFO_PACKET_RESPONSE				2
#define TDN_SEND_REQUEST_INFO_PACKET_RESPONSE_SIZE	2
#define TDN_SEND_REQUEST_INFO_SEND_TRY							3
#define SEND_REQUEST_INFO_MAX_SEND_TRYS							(CryMatchMakingConnectionTimeOut/CryLobbySendInterval)

#define GAMESPY_SESSION_ID_PROFILE_SHIFT	32
#define GAMESPY_SESSION_ID_TIME_MASK			0x00000000FFFFFFFFull

#define GAMESPY_ENCODE8BIT_INITIAL_DIVISOR		84ull										// pow( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE, floor( log( pow( 2, 8 ) - 1 ) / log( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE ) ) )
#define GAMESPY_ENCODE16BIT_INITIAL_DIVISOR		7056ull									// pow( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE, floor( log( pow( 2, 16 ) - 1 ) / log( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE ) ) )
#define GAMESPY_ENCODE32BIT_INITIAL_DIVISOR		4182119424ull						// pow( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE, floor( log( pow( 2, 32 ) - 1 ) / log( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE ) ) )
#define GAMESPY_ENCODE64BIT_INITIAL_DIVISOR		17490122876598091776ull	// pow( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE, floor( log( pow( 2, 64 ) - 1 ) / log( GAMESPY_ENCODE_NUMERIC_TABLE_SIZE ) ) )

#define GAMESPY_INVALID_SESSION_ID						0xFFFFFFFFFFFFFFFF
#define KEY_IS_CUSTOM_NUMERIC_BIT							( BIT( 6 ) )
#define KEY_IS_CUSTOM_STRING_BIT							( BIT( 7 ) )
#define KEY_IS_CUSTOM_MASK										( KEY_IS_CUSTOM_STRING_BIT | KEY_IS_CUSTOM_NUMERIC_BIT )
#define KEY_IS_CUSTOM_NUMERIC( keyID )				( ( ( keyID ) & KEY_IS_CUSTOM_MASK ) == KEY_IS_CUSTOM_NUMERIC_BIT )
#define KEY_IS_CUSTOM_STRING( keyID )					( ( ( keyID ) & KEY_IS_CUSTOM_MASK ) == KEY_IS_CUSTOM_STRING_BIT )
#define KEY_CUSTOM_NUMERIC_TO_STRING( keyID )	( ( keyID ) ^ KEY_IS_CUSTOM_MASK )

#define GS_SPECIALFLAG_FAILED_TO_JOIN_STAGING_ROOM			(BIT(0))

#define CD_KEY_CHALLENGE_CHARSET_SIZE	62						// CD key challenge strings are alphanumeric only
#define MAX_BASE62CHARS_PER_UINT32		5							// floor( log( UINT_MAX + 1 ) / log( CD_KEY_CHALLENGE_CHARSET_SIZE ) ), number of base 62 characters that a uint32 can hold
#define NUM_BASE62STRINGS_PER_UINT32	916132832ull	// pow( CD_KEY_CHALLENGE_CHARSET_SIZE, MAX_BASE62CHARS_PER_UINT32 ), number of base 62 strings that a uint32 can represent
#define FIRST_BASE62_UPPER						10						// First base 62 value represented by upper case letter
#define FIRST_BASE62_LOWER						36						// First base 62 value represented by lower case letter
																									// ip              port
#define MAX_ASCII_SOCKET_ADDRESS_LEN					21	// 123.123.123.123:64100

extern "C"
{
	_CRTIMP errno_t __cdecl rand_s ( _Out_ unsigned int *_RandomValue);
}

const char CCryGameSpyMatchMaking::k_encodeNumericKey[ GAMESPY_ENCODE_NUMERIC_TABLE_SIZE ] =
{
	'I', '*', 'v', '_', '#', '(', '9', '>', '5', 'C', '6', 'x', '-', '}', '[', '?',
	']', 's', 'p', 'o', '^', '2', '|', '0', '/', '4', 'S', 'w', '{', ' ', 'g', 'r',
	'3', '<', '1', 'q', 'J', '$', 'n', 'W', 'd', '+', 'b', 'B', 'G', 'Z', 'E', '&',
	'U', 'a', 'k', 'z', 'u', 'h', 'y', 'K', 'Q', 'H', 'i', '=', 'X', 'l', 'N', 'j',
	'c', ',', 'f', 'm', '`', '!', ':', '@', 'Y', '7', 't', '.', '8', ';', '~', ')',
	'T', 'V', 'e', '%'
};

const int8 CCryGameSpyMatchMaking::k_decodeNumericKey[ GAMESPY_DECODE_NUMERIC_TABLE_SIZE ] =
{
	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	29,  69,  -1,   4,  37,  83,  47,  -1,   5,  79,   1,  41,  65,  12,  75,  24,
	23,  34,  21,  32,  25,   8,  10,  73,  76,   6,  70,  77,  33,  59,   7,  15,
	71,  -1,  43,   9,  -1,  46,  -1,  44,  57,   0,  36,  55,  -1,  -1,  62,  -1,
	-1,  56,  -1,  26,  80,  48,  81,  39,  60,  72,  45,  14,  -1,  16,  20,   3,
	68,  49,  42,  64,  40,  82,  66,  30,  53,  58,  63,  50,  61,  67,  38,  19,
	18,  35,  31,  17,  74,  52,   2,  27,  11,  54,  51,  28,  22,  13,  78,  -1
};

void OnChangedsv_password(ICVar* pPass)
{
	CCryLobby* pLobby = static_cast<CCryLobby*>(gEnv->pNetwork->GetLobby());
	uint8 maxLen = 32;

	if (pLobby != NULL)
	{
		SConfigurationParams neededInfo[1] = { CLCC_MATCHMAKING_SESSION_PASSWORD_MAX_LENGTH, {NULL} };
		pLobby->GetConfigurationInformation(neededInfo, 1);
		maxLen = neededInfo[0].m_8;
	}

	if (strlen(pPass->GetString()) > maxLen)
	{
		TMemHdl h = pLobby->MemAlloc(maxLen + 1);
		char* pBuffer = static_cast<char*>(pLobby->MemGetPtr(h));
		CRY_ASSERT(pBuffer != NULL);
		if (pBuffer != NULL)
		{
			memset(pBuffer, 0, maxLen + 1);
			strncpy(pBuffer, pPass->GetString(), maxLen);
			pPass->Set(pBuffer);
			pLobby->MemFree(h);
		}
	}

#if defined(DEDICATED_SERVER)
	if (pLobby != NULL)
	{
		CCryGameSpyMatchMaking* pMatchMaking = static_cast<CCryGameSpyMatchMaking*>(pLobby->GetMatchMaking());
		if (pMatchMaking != NULL)
		{
			pMatchMaking->SessionPropagatePassword(CrySessionInvalidHandle, NULL, NULL, NULL);
		}
	}
#endif // defined(DEDICATED_SERVER)
}

// Description:
//	 Constructor for CCryGameSpyMatchMaking.
// Arguments:
//	 pLobby - lobby
//	 pService - lobby service
CCryGameSpyMatchMaking::CCryGameSpyMatchMaking( CCryLobby* pLobby, CCryLobbyService* pService, ECryLobbyService serviceType )
:	CCryMatchMaking( pLobby, pService, serviceType ),
	m_numServerReportKeys( 0 ),
	m_numServerQueryUserDataKeys( 0 ),
	m_numServerSearchReportKeys( 0 ),
	m_numServerSearchQueryKeys( 0 ),
	m_numPlayerKeys( 0 ),
	m_numTeamKeys( 0 ),
	m_cdKeyLocalIDCycle( 0 )
{
	for ( int i = 0; i < MAX_GAMESPY_SEARCHES; ++i )
	{
		ClearSessionSearch( &m_search[ i ] );
	}

	for ( int i = 0; i < MAX_MATCHMAKING_SESSIONS; ++i )
	{
		CCryMatchMaking::m_sessions[ i ] = &m_sessions[ i ];
		CCryMatchMaking::m_sessions[ i ]->localConnection = &m_sessions[ i ].localConnection;

		for ( int j = 0; j < MAX_LOBBY_CONNECTIONS; ++j )
		{
			CCryMatchMaking::m_sessions[ i ]->remoteConnection[ j ] = &m_sessions[ i ].remoteConnection[ j ];
		}
	}

	for ( int i = 0; i < MAX_MATCHMAKING_TASKS; ++i )
	{
		ClearTask( i );
		CCryMatchMaking::m_task[ i ] = &m_task[ i ];
	}

	if (gEnv->pConsole != NULL)
	{
		ICVar* pPass = gEnv->pConsole->GetCVar("sv_password");
		pPass->SetOnChangeCallback(OnChangedsv_password);
	}
}


// Description:
//	 Destructor for CCryGameSpyMatchMaking.
CCryGameSpyMatchMaking::~CCryGameSpyMatchMaking()
{
}


// Description:
//	 Initialise CCryGameSpyMatchMaking.
ECryLobbyError CCryGameSpyMatchMaking::Initialise()
{
	ECryLobbyError			result;

	result = CCryMatchMaking::Initialise();

	return result;
}


// Description:
//	 Terminate CCryGameSpyMatchMaking.
ECryLobbyError CCryGameSpyMatchMaking::Terminate()
{
	ECryLobbyError			result;

	result = CCryMatchMaking::Terminate();

	return result;
}

// Description:
//	 Tick the CCryGameSpyMatchMaking.
// Arguments:
//	 tv - time
void CCryGameSpyMatchMaking::Tick( CTimeValue tv )
{
	bool	ensureCorrectStagingRoom = true;

	CCryMatchMaking::Tick( tv );

	for ( uint32 i = 0; i < MAX_MATCHMAKING_TASKS; i++ )
	{
		STask*					pTask = &m_task[ i ];

#if ENABLE_CRYLOBBY_DEBUG_TESTS
		if (pTask->used)
		{
			if (m_lobby->DebugTickCallStartTaskRunning(pTask->lTaskID))
			{
				StartTaskRunning(i);
				continue;
			}
			if (!m_lobby->DebugOKToTickTask(pTask->lTaskID, pTask->running))
			{
				continue;
			}
		}
#endif

		if ( pTask->used && pTask->running )
		{
			if ( ( ( ( pTask->startedTask == eT_SessionCreate ) || ( pTask->startedTask == eT_SessionMigrateHostServer ) ) && ( m_sessions[ pTask->session ].createFlags & CRYSESSION_CREATE_FLAG_NUB ) ) || ( pTask->startedTask == eT_SessionEnsureCorrectStagingRoom ) )
			{
				ensureCorrectStagingRoom = false;
			}

			if ( ( pTask->error == eCLE_Success ) && ( CheckTaskGameSpyObjects( i ) == eTGSCR_Wait ) && !pTask->canceled )
			{
				continue;
			}

			switch ( pTask->subTask )
			{
			case eT_SessionCreate:

				TickSessionCreate( i );
				break;

			case eT_SessionUpdate:

				TickSessionUpdate( i );
				break;

#if !defined(DEDICATED_SERVER)
			case eT_SessionQuery:

				TickSessionQuery( i );
				break;

			case eT_SessionSearch:

				TickSessionSearch( i );
				break;

			case eT_SessionSearchWaitStart:
				TickSessionSearchWaitStart(i);
				break;

			case eT_SessionJoin:

				TickSessionJoin(i);
				break;
				
			case eT_SessionSendRequestInfo:
				TickSessionSendRequestInfo(i);
				break;

			case eT_SessionSendRequestInfoNAT:
				TickSessionSendRequestInfoNAT(i);
				break;

#endif // !defined(DEDICATED_SERVER)

			case eT_SessionJoinFindUseAddress:
				TickSessionJoinFindUseAddress(i);
				break;

			case eT_SessionStart:

				TickSessionStart(i);
				break;

			case eT_SessionEnd:

				TickSessionEnd(i);
				break;

			case eT_SessionEnsureCorrectStagingRoom:

				TickSessionEnsureCorrectStagingRoom( i );
				break;

#if NETWORK_HOST_MIGRATION

			case eT_SessionMigrateHostServer:

				TickHostMigrationServer( i );
				break;

			case eT_SessionMigrateHostFinish:

				TickHostMigrationFinish( i );
				break;

#endif
			default:
				TickBaseTask(i);
				break;
			}

			if ( ( pTask->canceled ) || ( pTask->error != eCLE_Success ) )
			{
				StopTaskRunning( i );
			}
		}
	}

	if ( ensureCorrectStagingRoom )
	{
		EnsureCorrectStagingRoom();
	}
}

void CCryGameSpyMatchMaking::EnsureCorrectStagingRoom() 
{
	CrySessionHandle	nubSessions[ 2 ];
	uint32						nubSessionCount = 0;
	bool							hostingNubSession = false;

	for ( CrySessionHandle i = 0; ( i < MAX_MATCHMAKING_SESSIONS ) && ( nubSessionCount < 2 ) && !hostingNubSession; ++i )
	{
		SSession*		pSession = &m_sessions[ i ];

		if ( ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_USED ) && ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) )
		{
			nubSessions[ nubSessionCount ] = i;
			++nubSessionCount;

			if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST )
			{
				hostingNubSession = true;
			}
		}
	}

	if ( !hostingNubSession )
	{
		switch ( nubSessionCount )
		{
		case 0:

			if ( !GetService()->PeerIsInCorrectStagingRoom( NULL ) )
			{
				SessionEnsureCorrectStagingRoom( CrySessionInvalidHandle );
			}
			break;

		case 1:

			if ( !GetService()->PeerIsInCorrectStagingRoom( &m_sessions[ nubSessions[ 0 ] ].id.m_addr ) && (m_sessions[nubSessions[0]].specialFlags & GS_SPECIALFLAG_FAILED_TO_JOIN_STAGING_ROOM)==0)
			{
				SessionEnsureCorrectStagingRoom( nubSessions[ 0 ] );
			}
			break;

		case 2:

			// Lobby merge in progress; can't know what staging room we should be in until it completes.
			break;
		}
	}
}


// Description:
//	 Called when a packet is received.
// Arguments:
//	 addr - sender
//	 pData - data
//	 length - length
void CCryGameSpyMatchMaking::OnPacket( const TNetAddress& addr, CCryLobbyPacket* pPacket )
{
	CCrySharedLobbyPacket* pSPacket = (CCrySharedLobbyPacket*)pPacket;

	switch (pSPacket->StartRead())
	{
	case eGameSpyPT_MM_RequestServerData:
		SendServerData( addr, pSPacket );
		break;

	case eGameSpyPT_MM_ServerData:
		ProcessServerData( addr, pSPacket );
		break;

	case eGameSpyPT_SessionRequestJoin:

		ProcessSessionRequestJoin( addr, pSPacket );
		break;

	case eGameSpyPT_SessionRequestJoinResult:
		ProcessSessionRequestJoinResult(addr, pSPacket);
		break;

	case eGameSpyPT_SessionAddRemoteConnections:
		ProcessSessionAddRemoteConnections(addr, pSPacket);
		break;

#if NETWORK_HOST_MIGRATION
	case eGameSpyPT_HostMigrationStart:
		ProcessHostMigrationStart(addr, pSPacket);
		break;

	case eGameSpyPT_HostMigrationServer:
		ProcessHostMigrationFromServer(addr, pSPacket);
		break;

	case eGameSpyPT_HostMigrationClient:
		ProcessHostMigrationFromClient( addr, pSPacket );
		break;
#endif

	case eGameSpyPT_UserData:

		ProcessLocalUserData( addr, pSPacket );
		break;

#if GAMESPY_ENABLE_SESSION_PING
	case eGameSpyPT_SessionPing:

		ProcessSessionPing( addr, pSPacket );
		break;

	case eGameSpyPT_SessionPong:

		ProcessSessionPong( addr, pSPacket );
		break;
#endif

	case eGameSpyPT_SessionRequestInfo:
		ProcessSessionRequestInfo(addr, pSPacket);
		break;

	case eGameSpyPT_SessionRequestInfoResponse:
		ProcessSessionRequestInfoResponse(addr, pSPacket);
		break;

	case eGameSpyPT_CDKeyChallenge:
		ProcessCDKeyChallenge( addr, pSPacket );
		break;

	case eGameSpyPT_CDKeyResponse:
		ProcessCDKeyResponse( addr, pSPacket );
		break;

	case eGameSpyPT_PropagatePassword:
		ProcessPropagatePassword(addr, pSPacket);
		break;

	default:
		CCryMatchMaking::OnPacket(addr, pSPacket);
		break;
	}
}


// Description:
//	 This function must be called by all peers before any other matchmaking
//	 calls. It defines the applications custom data used for it's sessions. This
//	 data is advertised when a host creates a session and gets returned when a
//	 session search is performed
// Arguments:
//	 pData - Pointer to an array of SCrySessionUserData that defines the applications custom data for a session
//	 numData - Number of items in the data array
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 pCbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionRegisterUserData( SCrySessionUserData* pData, uint32 numData, CryLobbyTaskID* pTaskID, CryMatchmakingCallback cb, void* pCbArg )
{
	ECryLobbyError			error = eCLE_Success;

	LOBBY_AUTO_LOCK;

	if ( numData < MAX_MATCHMAKING_SESSION_USER_DATA )
	{
		CryMatchMakingTaskID	mmTaskID;

		error = StartTask( eT_SessionRegisterUserData, &mmTaskID, pTaskID, 0, cb, pCbArg );

		if ( error == eCLE_Success )
		{
			uint32								neededInfoCount = 0;
			SConfigurationParams	neededInfo[ eCGSK_NumGameReservedKeys + eCGSK_NumCryNetworkOptionalKeys ];

			for ( uint8 i = 0; i < MAX_REGISTERED_KEYS; ++i )
			{
				m_keyUserDataIdx[ i ] = KEY_USERDATA_IDX_UNUSED;
				m_stringKeyToNumericKey[ i ] = KEY_USERDATA_IDX_UNUSED;
				m_numericKeyToStringKey[ i ] = KEY_USERDATA_IDX_UNUSED;
			}

			m_keyUserDataIdx[ eCGSK_HostNameKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_GameNameKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_NumPlayersKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_MaxPlayersKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_SessionIDKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_NumPrivateSlotsKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_NumPublicSlotsKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_RankedKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_CreateFlagsKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_NumMigrationsKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_PasswordRequiredKey ] = KEY_USERDATA_IDX_INTERNAL;
			m_keyUserDataIdx[ eCGSK_DedicatedKey ] = KEY_USERDATA_IDX_INTERNAL;

			for ( int i = 0; i < numData; ++i )
			{
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id > 0 ), "User data ID in unused range of GameSpy reserved GameSpy keys" );
				CRY_ASSERT_MESSAGE( !( ( pData[ i ].m_id > eCGSK_RegionKey ) && ( pData[ i ].m_id <= eCGSK_LastGameSpyReservedKey ) ), "User data ID in unused range of GameSpy reserved GameSpy key IDs" );
				CRY_ASSERT_MESSAGE( !( ( pData[ i ].m_id >= eCGSK_FirstCryNetworkReservedKey ) && ( pData[ i ].m_id <= eCGSK_LastCryNetworkReservedKey ) ), "User data ID in range of CryNetwork reserved GameSpy key IDs" );
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id != eCGSK_HostNameKey ), "User data ID equal to GameSpy key ID handled internally by CryNetwork" );
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id != eCGSK_GameNameKey ), "User data ID equal to GameSpy key ID handled internally by CryNetwork" );
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id != eCGSK_MaxPlayersKey ), "User data ID equal to GameSpy key ID handled internally by CryNetwork" );
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id != eCGSK_NumPlayersKey ), "User data ID equal to GameSpy key ID handled internally by CryNetwork" );
				CRY_ASSERT_MESSAGE( ( pData[ i ].m_id <= eCGSK_LastGameReservedKey ), "User data ID outside of range of valid GameSpy key IDs" );

				m_keyUserDataIdx[ pData[ i ].m_id ] = i;

				if ( pData[ i ].m_id >= eCGSK_FirstCryNetworkOptionalKey )
				{
					neededInfo[ neededInfoCount ].m_fourCCID = CLCC_GAMESPY_KEYNAME;
					neededInfo[ neededInfoCount ].m_8 = pData[ i ].m_id;
					++neededInfoCount;
				}
			}

			if ( m_keyUserDataIdx[ eCGSK_GameModeKey ] == KEY_USERDATA_IDX_UNUSED )
			{
				m_keyUserDataIdx[ eCGSK_GameModeKey ] = KEY_USERDATA_IDX_INTERNAL;
			}

			// Get names and register keys.

			m_lobby->GetConfigurationInformation( neededInfo, neededInfoCount );

			neededInfoCount = 0;

			for ( int i = 0; i < numData; ++i )
			{
				if ( pData[ i ].m_id >= eCGSK_FirstCryNetworkOptionalKey )
				{
					uint32					numNames = 0;
					const char*			pName = ( const char* )neededInfo[ neededInfoCount ].m_pData;

					if ( pName )
					{
						while ( *pName )
						{
							switch ( numNames )
							{
							case 0:
								qr2_register_key( pData[ i ].m_id, pName );
								break;
							case 1:
								{
									uint8 j;

									for ( j = 0; j < MAX_REGISTERED_KEYS; ++j )
									{
										if ( !strcmp( qr2_registered_key_list[ j ], pName ) )
										{
											break;
										}
									}

									if ( j == MAX_REGISTERED_KEYS )
									{
										j = KEY_CUSTOM_NUMERIC_TO_STRING( pData[ i ].m_id );
										qr2_register_key( j, pName );
									}

									m_numericKeyToStringKey[ pData[ i ].m_id ] = j;
									m_stringKeyToNumericKey[ j ] = pData[ i ].m_id;
									m_keyUserDataIdx[ j ] = KEY_USERDATA_IDX_INTERNAL;
								}
								break;
							default:
								break;
							}

							pName += strlen( pName ) + 1;
							++numNames;
						}
					}

					CRY_ASSERT_MESSAGE( ( ( numNames > 0 ) && ( numNames < 3 ) ), "Invalid number of names for key" );

					++neededInfoCount;
				}
			}

			qr2_register_key( eCGSK_SessionIDKey, k_sessionIDKeyName );
			qr2_register_key( eCGSK_NumPrivateSlotsKey, k_numPrivateSlotsKeyName );
			qr2_register_key( eCGSK_NumPublicSlotsKey, k_numPublicSlotsKeyName );
			qr2_register_key( eCGSK_RankedKey, k_rankedKeyName );
			qr2_register_key( eCGSK_CreateFlagsKey, k_createFlagsKeyName );
			qr2_register_key( eCGSK_NumMigrationsKey, k_numMigrationsKeyName );
			qr2_register_key( eCGSK_PasswordRequiredKey, k_passwordRequiredKeyName );
			qr2_register_key( eCGSK_DedicatedKey, k_dedicatedKeyName );

			for ( uint8 i = 0; i < MAX_REGISTERED_KEYS; ++i )
			{
				if ( m_keyUserDataIdx[ i ] == KEY_USERDATA_IDX_UNUSED )
				{
					continue;
				}

				bool						query = true;
				bool						report = true;
				bool						searchQuery = true;
				bool						searchReport = true;

				if ( KEY_IS_CUSTOM_STRING( i ) )
				{
					query = false;
					searchQuery = false;
					searchReport = false;
				}

				switch ( i )
				{
				case eCGSK_GameNameKey:

					query = false;
					searchQuery = false;
					searchReport = false;
					break;

				case eCGSK_RegionKey:
					query = false;
					report = false;
					break;

				case eCGSK_SessionIDKey:
					query = false;
					break;

				case eCGSK_HostNameKey:
				case eCGSK_GameModeKey:

					searchQuery = false;
					break;

				case eCGSK_MaxPlayersKey:

					searchReport = false;
					break;
				}

				if ( report )
				{
					m_serverReportKeys[ m_numServerReportKeys ] = i;
					++m_numServerReportKeys;
				}

				if ( query && ( m_keyUserDataIdx[ i ] != KEY_USERDATA_IDX_INTERNAL ) )
				{
					m_serverQueryUserDataKeys[ m_numServerQueryUserDataKeys ] = i;
					++m_numServerQueryUserDataKeys;
				}

				if ( searchQuery )
				{
					m_serverSearchQueryKeys[ m_numServerSearchQueryKeys ] = i;
					++m_numServerSearchQueryKeys;
				}

				if ( searchReport )
				{
					m_serverSearchReportKeys[ m_numServerSearchReportKeys ] = i;
					++m_numServerSearchReportKeys;
				}
			}

			memcpy( m_registeredUserData.data, pData, numData * sizeof ( pData[ 0 ] ) );
			m_registeredUserData.num = numData;

			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}
	else
	{
		error = eCLE_OutOfSessionUserData;
	}

	CryLogAlways( "[Lobby] Start SessionRegisterUserData error %d", error );

	return error;
}


// Description:
//	 Called by the host to create a session and make it available for others to join.
// Arguments:
//	 users - Pointer to an array of local users who will be joining the session after creation. The first user will be the session owner
//							The numbers given should be the pad numbers the users are using.
//							It is important that these are correct on live as multiple accounts can be signed in one for each pad.
//							If the wrong pad numbers are passed in then the wrong accounts will be used for the session.
//							Remember even on a single account machine and single player game the user doesn't have to be using and signed in on pad 0
//	 numUsers - Number of users in the users array
//	 flags - Session create flags that control how the session behaves
//	 data - Pointer to a SCrySessionData that describes the session
//	 taskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 cbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionCreate( uint32* users, int numUsers, uint32 flags, SCrySessionData* data, CryLobbyTaskID* taskID, CryMatchmakingSessionCreateCallback cb, void* cbArg )
{
	if (gEnv->IsDedicated())
	{
		CRY_ASSERT_MESSAGE(numUsers == 1,"Dedicated Server, but number of users on create != 1 - Being forced to 0");
		numUsers=0;
	}
	else
	{
		// Multiple local users not supported.
		assert( numUsers == 1 );
		assert( users[ 0 ] == 0 );
	}

	LOBBY_AUTO_LOCK;

	CrySessionHandle		h;
	ECryLobbyError			error = CreateSessionHandle( &h, true, flags, numUsers );

	// Public/private slots not supported yet.
	data->m_numPublicSlots += data->m_numPrivateSlots;
	data->m_numPrivateSlots = 0;

	if ( error == eCLE_Success )
	{
		SSession*				pSession = &m_sessions[ h ];
		CryMatchMakingTaskID	tid;

		error = StartTask( eT_SessionCreate, &tid, taskID, h, ( void* )cb, cbArg );

		if ( error == eCLE_Success )
		{
			STask*								pTask = &m_task[ tid ];
			SSessionCreateParam		param;

			param.flags = 0;

			error = CreateTaskParam( tid, CREATE_CONTROL_PARAM, &param, 1, sizeof ( SSessionCreateParam ) );

			if ( error == eCLE_Success )
			{
				error = CreateTaskParam( tid, CREATE_SESSION_DATA_PARAM, data, 1, sizeof ( SCrySessionData ) );

				if ( error == eCLE_Success )
				{
					error = CreateTaskParam( tid, CREATE_SESSION_USER_DATA_PARAM, data->m_data, data->m_numData, data->m_numData * sizeof ( SCryLobbyUserData ) );

					if ( error == eCLE_Success )
					{
						SCrySessionData*		pSessionData = ( SCrySessionData* )m_lobby->MemGetPtr( pTask->params[ CREATE_SESSION_DATA_PARAM ] );

						pSessionData->m_data = ( SCryLobbyUserData* )m_lobby->MemGetPtr( pTask->params[ CREATE_SESSION_USER_DATA_PARAM ] );

#if NETWORK_HOST_MIGRATION

						pSession->newHostPriorityCount = 0;
						pSession->newHostUID = CryMatchMakingInvalidConnectionUID;

#endif

						CryLogAlways( "[Lobby] Created local connection uid %d", pSession->localConnection.uid.m_uid );
						FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
					}
				}
			}
		}

		if ( error != eCLE_Success )
		{
			FreeSessionHandle( h );
		}
	}

	CryLogAlways( "[Lobby] Start SessionCreate error %d", error );

	return error;
}


// Description:
//	 Called to join a session previously created by a session host. Session
//	 id's can be obtained from a session search or from a session invite
//	 notification.
// Arguments:
//	 pUsers - Pointer to an array of pad numbers of the local users who will be joining the session
//	 numUsers - Number of users in the users array
//	 flags - Session create flags that control how the session behaves
//	 id - A CrySessionID of the session that will be joined
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 pCbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if
//	 function failed to start.
ECryLobbyError CCryGameSpyMatchMaking::SessionJoin( uint32* pUsers, int numUsers, uint32 flags, CrySessionID id, CryLobbyTaskID* pTaskID, CryMatchmakingSessionJoinCallback cb, void* pCbArg )
{
#if !defined(DEDICATED_SERVER)
	LOBBY_AUTO_LOCK;

	CrySessionHandle		h;
	ECryLobbyError			error = CreateSessionHandle( &h, false, flags, numUsers );

	if ( error == eCLE_Success )
	{
		CryMatchMakingTaskID	tid;

		error = StartTask( eT_SessionJoin, &tid, pTaskID, h, ( void* )cb, pCbArg );

		if ( error == eCLE_Success )
		{
			error = CreateTaskParam( tid, JOIN_PARAM, NULL, 1, sizeof ( SSessionJoinParam ) );

			if ( error == eCLE_Success )
			{
				STask*							pTask = &m_task[ tid ];
				SSessionJoinParam*	pParam = ( SSessionJoinParam* )( m_lobby->MemGetPtr( pTask->params[ JOIN_PARAM ] ) );

				pParam->finishedCryNetworkJoin = false;
				m_sessions[ h ].id = *( ( SCryGameSpySessionID* )id.get() );
				m_sessions[ h ].localConnection.uid.m_sid = m_sessions[ h ].id.m_sid;
				FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
			}
		}
		
		if(error != eCLE_Success)
		{
			FreeSessionHandle( h );
		}
	}

	CryLogAlways( "[Lobby] Start SessionJoin error %d", error );

	return error;
#else
	return eCLE_InvalidRequest;
#endif //  !defined(DEDICATED_SERVER)
}

// Description:
//	 Performs a search for sessions that match the specified search criteria.
// Arguments:
//	 user - The pad number of the user doing the search.
//							In Live the TrueSkill system is used. Each session has a skill value worked out from the members skill values.
//							The skill value of the user doing the search is used to return sessions that have skill values closest to the users value.
//	 param - Pointer to a SCrySessionSearchParam that defines how sessions should be filtered
//	 taskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 cbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionSearch( uint32 user, SCrySessionSearchParam* pParam, CryLobbyTaskID* pTaskID, CryMatchmakingSessionSearchCallback cb, void* pCbArg )
{
#if !defined(DEDICATED_SERVER)
	LOBBY_AUTO_LOCK;

	ECryLobbyError			error = eCLE_Success;
	CryMatchMakingTaskID	tid;

	error = StartTask( eT_SessionSearch, &tid, pTaskID, CrySessionInvalidHandle, ( void* )cb, pCbArg );

	if ( ( error == eCLE_Success ) && pParam && pParam->m_data )
	{
		error = CreateTaskParam( tid, SEARCH_PARAM_GAME_PARAM, pParam, 1, sizeof ( SCrySessionSearchParam ) );

		if ( error == eCLE_Success )
		{
			error = CreateTaskParam( tid, SEARCH_PARAM_GAME_PARAM_DATA, pParam->m_data, pParam->m_numData, pParam->m_numData * sizeof( pParam->m_data[ 0 ] ) );
		}
	}

	if ( error == eCLE_Success )
	{
		FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
	}

	NetLog( "[Lobby] Start SessionSearch error %d", error );

	return error;
#else
	return eCLE_InvalidRequest;
#endif // !defined(DEDICATED_SERVER)
}


// Description:
//	 Called by all members of the session to delete it when they have finished with it.
// Arguments:
//	 h - Handle to the session to delete
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 pCbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionDelete( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, CryMatchmakingCallback cb, void* pCbArg )
{
	ECryLobbyError			error = eCLE_Success;

	CrySessionHandle		localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ( ( localSessionIndex < MAX_MATCHMAKING_SESSIONS) && (m_sessions[ localSessionIndex ].localFlags & CRYSESSION_LOCAL_FLAG_USED) )
	{
		SSession* pSession = &m_sessions[localSessionIndex];
		CryMatchMakingTaskID tid;

		error = StartTask( eT_SessionDelete, &tid, pTaskID, localSessionIndex, ( void* )cb, pCbArg );

		if ( error == eCLE_Success )
		{
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
		}
	}
	else
	{
		error = eCLE_SuccessInvalidSession;
	}

	CryLogAlways( "[Lobby] Start SessionDelete error %d", error );

	return error;
}


ECryLobbyError CCryGameSpyMatchMaking::SessionMigrate( CrySessionHandle gameSessionHandle, uint32* pUsers, int numUsers, uint32 flags, SCrySessionData* pData, CryLobbyTaskID* pTaskID, CryMatchmakingSessionCreateCallback pCB, void* pCBArg )
{
	ECryLobbyError error = eCLE_Success;
	CrySessionHandle localSessionIndex;

	LOBBY_AUTO_LOCK;

	// Because we simply want to re-use the session that is already available, we don't need to do much here

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ( ( localSessionIndex < MAX_MATCHMAKING_SESSIONS ) && ( m_sessions[ localSessionIndex ].localFlags & CRYSESSION_LOCAL_FLAG_USED ) )
	{
		SSession* pSession = &m_sessions[ localSessionIndex ];
		CryMatchMakingTaskID mmTaskID;

		error = StartTask( eT_SessionMigrate, &mmTaskID, pTaskID, localSessionIndex, ( void* )pCB, pCBArg );

		if ( error == eCLE_Success )
		{
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	CryLogAlways("[Lobby] Start SessionMigrate error %d", error);

	return error;
}


// Description:
//	 Called by the host to update the applications custom data. Only the data
//	 items that need updating need to be given.
// Arguments:
//	 gameSessionHandle - Handle to the session to be updated
//	 pData - Pointer to an array of SCrySessionUserData that contains the applications custom data that will be updated for the session
//	 numData - Number of items in the data array
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 pCbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionUpdate( CrySessionHandle gameSessionHandle, SCrySessionUserData* pData, uint32 numData, CryLobbyTaskID* pTaskID, CryMatchmakingCallback cb, void* pCbArg )
{
	ECryLobbyError				error = eCLE_InvalidSession;

	CrySessionHandle			localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ( localSessionIndex < MAX_MATCHMAKING_SESSIONS )
	{
		SSession*							pSession = &m_sessions[ localSessionIndex ];
	
		if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_USED )
		{
			if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST )
			{
				CryMatchMakingTaskID	tid;

				error = StartTask( eT_SessionUpdate, &tid, pTaskID, localSessionIndex, ( void* )cb, pCbArg );

				if ( error == eCLE_Success )
				{
					STask*								pTask = &m_task[ tid ];

					error = CreateTaskParam( tid, UPDATE_SESSION_USER_DATA_PARAM, pData, numData, numData * sizeof ( SCrySessionUserData ) );

					if ( error == eCLE_Success )
					{
						FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
					}
				}
			}
			else
			{
				error = eCLE_InvalidRequest;
			}
		}
	}

	NetLog("[Lobby] Start SessionUpdate error %d", error);

	return error;
}

ECryLobbyError CCryGameSpyMatchMaking::SessionUpdateSlots(CrySessionHandle h, uint32 numPublic, uint32 numPrivate, CryLobbyTaskID* pTaskID, CryMatchmakingCallback pCB, void* pCBArg)
{
	ECryLobbyError error = eCLE_Success;

	LOBBY_AUTO_LOCK;

	h = GetSessionHandleFromGameSessionHandle(h);

	if ((h < MAX_MATCHMAKING_SESSIONS) && (m_sessions[h].localFlags & CRYSESSION_LOCAL_FLAG_USED))
	{
		CryMatchMakingTaskID tid;

		error = StartTask(eT_SessionUpdateSlots, &tid, pTaskID, h, (void*)pCB, pCBArg);

		if (error == eCLE_Success)
		{
			FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, tid);
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	NetLog("[Lobby] Start SessionUpdateSlots return %d", error);

	return error;
}


// Description:
//	 Called by any one to retrieve the current state of the session.
// Arguments:
//	 h - Handle to the session to query
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 cbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionQuery( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, CryMatchmakingSessionQueryCallback pCB, void* pCBArg )
{
#if !defined(DEDICATED_SERVER)
	ECryLobbyError			error = eCLE_Success;

	CrySessionHandle		localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ((localSessionIndex < MAX_MATCHMAKING_SESSIONS) && (m_sessions[localSessionIndex].localFlags & CRYSESSION_LOCAL_FLAG_USED))
	{
		CryMatchMakingTaskID tid;

		error = StartTask(eT_SessionQuery, &tid, pTaskID, localSessionIndex, (void*)pCB, pCBArg);

		if (error == eCLE_Success)
		{
			SSessionQueryParam	param;

			param.started = false;
			param.finished = false;
			param.requirePassword = false;
			error = CreateTaskParam( tid, QUERY_PARAM, &param, 1, sizeof ( SSessionQueryParam ) );

			if ( error == eCLE_Success )
			{
				FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, tid);
			}
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	NetLog("[Lobby] Start SessionQuery error %d", error);

	return error;
#else
	return eCLE_InvalidRequest;
#endif // !defined(DEDICATED_SERVER)
}


// Description:
//	 Called by any one to retrieve the list of users in the session.
// Arguments:
//	 h - Handle to the session to get user information from
//	 pTaskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 cb - Callback function that is called when function completes
//	 pCBArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 eCLE_Success if function successfully started or an error code if function failed to start
ECryLobbyError CCryGameSpyMatchMaking::SessionGetUsers( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, CryMatchmakingSessionGetUsersCallback pCB, void* pCBArg )
{
	ECryLobbyError			error = eCLE_Success;

	CrySessionHandle		localSessionHandle;

	LOBBY_AUTO_LOCK;

	localSessionHandle = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ( ( localSessionHandle < MAX_MATCHMAKING_SESSIONS ) && (m_sessions[localSessionHandle].localFlags & CRYSESSION_LOCAL_FLAG_USED) )
	{
		CryMatchMakingTaskID tid;

		error = StartTask( eT_SessionGetUsers, &tid, pTaskID, localSessionHandle, ( void* )pCB, pCBArg );

		if (error == eCLE_Success)
		{
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, tid );
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	if ( error != eCLE_Success )
	{
		CryLogAlways( "[Lobby] Start SessionQuery error %d", error );
	}

	return error;
}


ECryLobbyError CCryGameSpyMatchMaking::SessionStart( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, CryMatchmakingCallback cb, void* pCbArg )
{
	ECryLobbyError error = eCLE_Success;
	CrySessionHandle localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle(gameSessionHandle);

	if ((localSessionIndex < MAX_MATCHMAKING_SESSIONS) && (m_sessions[localSessionIndex].localFlags & CRYSESSION_LOCAL_FLAG_USED))
	{
		SSession* pSession = &m_sessions[localSessionIndex];
		CryMatchMakingTaskID tid;

		error = StartTask(eT_SessionStart, &tid, pTaskID, localSessionIndex, (void*)cb, pCbArg);

		if (error == eCLE_Success)
		{
			pSession->localFlags |= CRYSESSION_LOCAL_FLAG_STARTED;
			FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, tid);
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	NetLog("[Lobby] Start SessionStart error %d", error);

	return error;
}


ECryLobbyError CCryGameSpyMatchMaking::SessionEnd( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, CryMatchmakingCallback cb, void* pCbArg )
{
	ECryLobbyError error = eCLE_Success;
	CrySessionHandle localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle(gameSessionHandle);

	if ((localSessionIndex < MAX_MATCHMAKING_SESSIONS) && (m_sessions[localSessionIndex].localFlags & CRYSESSION_LOCAL_FLAG_USED))
	{
		SSession* pSession = &m_sessions[localSessionIndex];
		CryMatchMakingTaskID tid;

		error = StartTask(eT_SessionEnd, &tid, pTaskID, localSessionIndex, (void*)cb, pCbArg);

		if (error == eCLE_Success)
		{
			pSession->localFlags &= ~CRYSESSION_LOCAL_FLAG_STARTED;
			FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, tid);
		}
	}
	else
	{
		error = eCLE_SuccessInvalidSession;
	}

	NetLog("[Lobby] Start SessionEnd error %d", error);

	return error;
}


// Description:
//	 Set the local user data for the given user. This data gets sent around
//	 the system and is returned for each user in a session with a call to
//	 SessionGetUsers.
// Arguments:
//	 h - The handle of the session to set the user data for.
//	 taskID - Pointer to buffer to store the task ID to identify this call in the callback
//	 user - The pad number of the user whose data to set.
//	 pData - The data you wish to set for the user.
//	 dataSize - The size of the data max CRYLOBBY_USER_DATA_SIZE_IN_BYTES
//	 cb - Callback function that is called when function completes
//	 cbArg - Pointer to application-specified data that is passed to the callback
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyMatchMaking::SessionSetLocalUserData( CrySessionHandle gameSessionHandle, CryLobbyTaskID* pTaskID, uint32 user, uint8* pData, uint32 dataSize, CryMatchmakingCallback pCB, void* pCBArg )
{
	ECryLobbyError			error = eCLE_Success;
	CrySessionHandle		localSessionIndex;

	LOBBY_AUTO_LOCK;

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ((localSessionIndex < MAX_MATCHMAKING_SESSIONS) && (m_sessions[localSessionIndex].localFlags & CRYSESSION_LOCAL_FLAG_USED))
	{
		if (dataSize <= CRYLOBBY_USER_DATA_SIZE_IN_BYTES)
		{
			SSession* pSession = &m_sessions[localSessionIndex];
			SSession::SLConnection* pLConnection = &pSession->localConnection;
			CryMatchMakingTaskID mmTaskID;

			memcpy(pLConnection->userData, pData, dataSize);

			error = StartTask(eT_SessionSetLocalUserData, &mmTaskID, pTaskID, localSessionIndex, (void*)pCB, pCBArg);

			if (error == eCLE_Success)
			{
				FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID);
			}
		}
		else
		{
			error = eCLE_OutOfUserData;
		}
	}
	else
	{
		error = eCLE_InvalidSession;
	}

	return error;
}


CrySessionID CCryGameSpyMatchMaking::SessionGetCrySessionIDFromCrySessionHandle( CrySessionHandle gameSessionHandle )
{
	CrySessionID			result = 0;

	CrySessionHandle		localSessionIndex;

	localSessionIndex = GetSessionHandleFromGameSessionHandle( gameSessionHandle );

	if ( ( localSessionIndex < MAX_MATCHMAKING_SESSIONS ) && (m_sessions[localSessionIndex].localFlags & CRYSESSION_LOCAL_FLAG_USED) )
	{
		SCryGameSpySessionID*	pID = new SCryGameSpySessionID;

		if (pID)
		{
			( *pID ) = m_sessions[ localSessionIndex ].id;
			result = pID;
		}
	}

	return result;
}


uint32 CCryGameSpyMatchMaking::GetSessionIDSizeInPacket() const
{
	return SCryGameSpySessionID::GetSizeInPacket();
}


ECryLobbyError CCryGameSpyMatchMaking::WriteSessionIDToPacket( CrySessionID sessionId, CCryLobbyPacket* pPacket ) const
{
	ECryLobbyError result = eCLE_InternalError;

	if (pPacket)
	{
		SCryGameSpySessionID blank;
		memset(&blank, 0, sizeof(blank));

		if (sessionId != CrySessionInvalidID)
		{
			SCryGameSpySessionID*	pID = ( SCryGameSpySessionID* )sessionId.get();

			if ( pID )
			{
				pID->WriteToPacket( pPacket );
			}
			else
			{
				blank.WriteToPacket(pPacket);
			}
		}
		else
		{
			blank.WriteToPacket(pPacket);
		}
		
		result = eCLE_Success;
	}

	return result;
}


CrySessionID CCryGameSpyMatchMaking::ReadSessionIDFromPacket( CCryLobbyPacket* pPacket ) const
{
	if (pPacket)
	{
		SCryGameSpySessionID*	pID = new SCryGameSpySessionID();
		CrySessionID					returnId = pID;

		if (pID)
		{
			pID->ReadFromPacket( pPacket );

			SCryGameSpySessionID blank;
			memset(&blank, 0, sizeof(blank));
			
			//-- Can't use the SCryGameSpySessionID equals operator, because that only compares the m_sid, not everything we wrote to 
			//-- the packet to signify a null session ID. Use a field by field comparison instead.

			SCryGameSpyRemoteConnectionID& inc = pID->m_addr;
			SCryGameSpyRemoteConnectionID& loc = blank.m_addr;

			if ((pID->m_sid != blank.m_sid) || (inc.m_privateIP != loc.m_privateIP) || (inc.m_publicIP != loc.m_publicIP) ||
				(inc.m_privatePort != loc.m_privatePort) || (inc.m_publicPort != loc.m_publicPort))
			{
				//-- assume it's a valid session id if any field doesn't match our blanks
				return returnId;
			}
		}
	}

	return CrySessionInvalidID;
}


void CCryGameSpyMatchMaking::Kick(const CryUserID* pUserID, EDisconnectionCause cause)
{
	if (pUserID != NULL)
	{
		const SCryGameSpyUserID* pGameSpyUserID = static_cast<const SCryGameSpyUserID*>(pUserID->get());
		KickCmd(CryLobbyInvalidConnectionID, pGameSpyUserID->m_profileID, NULL, cause);
	}
}

ECryLobbyError CCryGameSpyMatchMaking::SessionEnsureBestHost( CrySessionHandle gh, CryLobbyTaskID* pTaskID, CryMatchmakingCallback pCB, void* pCBArg )
{
	ECryLobbyError				error = eCLE_Success;

#if NETWORK_HOST_MIGRATION
	LOBBY_AUTO_LOCK;

	CrySessionHandle localSessionIndex = GetSessionHandleFromGameSessionHandle( gh );

	if ( localSessionIndex != CrySessionInvalidHandle )
	{
		CryMatchMakingTaskID	mmTaskID;
		SSession*							pSession = &m_sessions[ localSessionIndex ];

		error = StartTask( eT_SessionEnsureBestHost, &mmTaskID, pTaskID, localSessionIndex, pCB, pCBArg );

		if ( error == eCLE_Success )
		{
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}

#endif

	return error;
}

#if defined(DEDICATED_SERVER)
ECryLobbyError CCryGameSpyMatchMaking::SessionPropagatePassword(CrySessionHandle gh, CryLobbyTaskID* pTaskID, CryMatchmakingCallback pCB, void* pCBArg)
{
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;
	CryMatchMakingTaskID mmTaskID;

	for (uint32 index = 0; index < MAX_MATCHMAKING_SESSIONS; ++index)
	{
		SSession* pSession = &m_sessions[index];
		if ( ((pSession->localFlags & (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) == (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) && (pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB) )
		{
			error = StartTask(eT_SessionPropagatePassword, &mmTaskID, pTaskID, index, pCB, pCBArg);
			if (error == eCLE_Success)
			{
				FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID);
				break;
			}
		}
	}

	return error;
}

void CCryGameSpyMatchMaking::StartSessionPropagatePassword(CryMatchMakingTaskID mmTaskID)
{
	STask* pTask = &m_task[mmTaskID];
	CrySessionHandle h = pTask->session;
	SSession* pSession = &m_sessions[h];

	SConfigurationParams neededInfo[1] = { CLCC_MATCHMAKING_SESSION_PASSWORD_MAX_LENGTH, {NULL} };
	m_lobby->GetConfigurationInformation(neededInfo, 1);
	const uint32 maxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT8Size + neededInfo[0].m_8;

	CCryLobbyPacket packet;
	if (packet.CreateWriteBuffer(maxBufferSize))
	{
		packet.StartWrite(eGameSpyPT_PropagatePassword, true);

		if (gEnv->pConsole != NULL)
		{
			ICVar* pCVar = gEnv->pConsole->GetCVar("sv_password");
			if (pCVar != NULL)
			{
				const char* pPassword = pCVar->GetString();
				const uint8 length = (uint8)strlen(pPassword);
				packet.WriteUINT8(length);
				packet.WriteString(pPassword, length + 1);
				NetLog("[Password]: Informing players of server password change to '%s'", pPassword);
				SendToAll(CryMatchMakingInvalidTaskID, &packet, h, CryMatchMakingInvalidConnectionID);
			}
		}

		packet.FreeWriteBuffer();
	}

}
#endif // defined(DEDICATED_SERVER)

void CCryGameSpyMatchMaking::ProcessPropagatePassword(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket)
{
	const uint8 len = pPacket->ReadUINT8();
	TMemHdl h = m_lobby->MemAlloc(len + 1);
	char* pBuffer = static_cast<char*>(m_lobby->MemGetPtr(h));

	pPacket->ReadString(pBuffer, len + 1);

	if (gEnv->pConsole != NULL)
	{
		ICVar* pCVar = gEnv->pConsole->GetCVar("sv_password");
		if (pCVar != NULL)
		{
			pCVar->Set(pBuffer);
			NetLog("[Password]: received password update to '%s'", pBuffer);
		}
	}

	m_lobby->MemFree(h);
}

#if NETWORK_HOST_MIGRATION


ECryLobbyError CCryGameSpyMatchMaking::HostMigrationServer( SHostMigrationInfo* pHostMigrationInfo )
{
	ECryLobbyError				error = eCLE_Success;
	CrySessionHandle			localSessionIdx = CrySessionInvalidHandle;
	SSession*							pSession = NULL;
	CryMatchMakingTaskID	mmTaskID;

	LOBBY_AUTO_LOCK;

	localSessionIdx = GetSessionHandleFromGameSessionHandle( pHostMigrationInfo->m_session );
	pSession = &m_sessions[ localSessionIdx ];
	error = StartTask( eT_SessionMigrateHostServer, &mmTaskID, NULL, localSessionIdx, NULL, NULL );

	if ( error == eCLE_Success )
	{
		error = CreateTaskParam( mmTaskID, MIGRATE_SERVER_PARAM, NULL, 1, sizeof ( SMigrateServerParam ) );

		if ( error == eCLE_Success )
		{
			STask*								pTask = &m_task[ mmTaskID ];
			SMigrateServerParam*	pParam = static_cast< SMigrateServerParam* >( m_lobby->MemGetPtr( pTask->params[ MIGRATE_SERVER_PARAM ] ) );

			pParam->flags = 0;
#if HOST_MIGRATION_SOAK_TEST_BREAK_DETECTION
			DetectHostMigrationTaskBreak(localSessionIdx, "HostMigrationServer()");
#endif
			pSession->hostMigrationInfo.m_taskID = pTask->lTaskID;
			
			NetLog( "[Host Migration]: matchmaking migrating session [%d] on the SERVER", localSessionIdx );
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}

	if (error != eCLE_Success)
	{
		NetLog( "[Host Migration]: CCryLANMatchMaking::HostMigrationServer(): session %d, error %d", localSessionIdx, error );

		m_hostMigration.Terminate( static_cast< SHostMigrationInfo_Private* >( pHostMigrationInfo ) );
	}

	return error;
}


void CCryGameSpyMatchMaking::StartHostMigrationServer( CryMatchMakingTaskID mmTaskID )
{
	STask*						pTask = &m_task[ mmTaskID ];
	SSession*					pSession = &m_sessions[ pTask->session ];

	if ( CreateTaskLink( mmTaskID ) )
	{
		++pSession->numMigrations;

		if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
		{
			CCryGameSpyLobbyService*	pService = GetService();

			pService->PeerLeaveStagingRoom( NULL, NULL );
			pService->PeerCreateStagingRoom( pSession->data.m_name, pSession->data.m_numPrivateSlots + pSession->data.m_numPublicSlots, 0, HostMigrationServerPeerJoinRoomCallback, pTask->pLink );
		}
	}
	else
	{
		UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
		m_hostMigration.Terminate( &pSession->hostMigrationInfo );
	}
}


void CCryGameSpyMatchMaking::TickHostMigrationServer( CryMatchMakingTaskID mmTaskID )
{
	STask*								pTask = &m_task[ mmTaskID ];
	SSession*							pSession = &m_sessions[ pTask->session ];
	SMigrateServerParam*	pMigrateServerParam = static_cast< SMigrateServerParam* >( m_lobby->MemGetPtr( pTask->params[ MIGRATE_SERVER_PARAM ] ) );

	if ( ( ( pMigrateServerParam->flags & eMSF_FinishedCreate ) || !( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) ) && !( pMigrateServerParam->flags & eMSF_SentPacket ) )
	{
		CCrySharedLobbyPacket	packet;
		const uint32					maxBufferSize = CryLobbyPacketHeaderSize + ( CryLobbyPacketUINT32Size * 2 );

		if ( packet.CreateWriteBuffer( maxBufferSize ) )
		{
			pSession->localFlags |= CRYSESSION_LOCAL_FLAG_HOST;
			pSession->hostConnectionID = CryMatchMakingInvalidConnectionID;
			pSession->id.m_addr = CreateRemoteConnectionID( GetPrivateIP(), GetPrivatePort(), GetPublicIP(), GetPublicPort(), GetPrivateIP(), GetPrivatePort() );

			if ( m_keyUserDataIdx[ eCGSK_HostProfileKey ] != KEY_USERDATA_IDX_UNUSED )
			{
				pSession->data.m_data[ m_keyUserDataIdx[ eCGSK_HostProfileKey ] ].m_int32 = GetService()->GetProfileID();
			}

			packet.StartWrite( eGameSpyPT_HostMigrationServer, true );
			packet.WriteUINT32( mmTaskID );
			packet.WriteUINT32( pSession->numMigrations );
			SendToAll( mmTaskID, &packet, pTask->session, CryMatchMakingInvalidConnectionID );
			pMigrateServerParam->flags |= eMSF_SentPacket;
			pTask->StartTimer();
			StartSubTask( eT_SessionMigrateHostFinish, mmTaskID );

			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

				CreateSessionURL( pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_sid, url );
				GetService()->GPSetLocationString( url.c_str() );
				PushUserData( pTask->session );
			}

			pSession->hostMigrationInfo.m_sessionMigrated = true;
		}
		else
		{
			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				GetService()->PeerLeaveStagingRoom( NULL, NULL );
			}

			UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
		}
	}

	if ( ( ( pMigrateServerParam->flags & ( eMSF_FinishedCreate | eMSF_StartedGame ) ) == eMSF_FinishedCreate ) && ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_STARTED ) )
	{
		GetService()->PeerStartGame();
		pMigrateServerParam->flags |= eMSF_StartedGame;
	}
}


void CCryGameSpyMatchMaking::TickHostMigrationFinish( CryMatchMakingTaskID mmTaskID )
{
	STask*		pTask = &m_task[ mmTaskID ];
	SSession*	pSession = &m_sessions[ pTask->session ];
	bool			finished = true;

	for ( uint32 i = 0; ( i < MAX_LOBBY_CONNECTIONS ) && finished; ++i )
	{
		if ( pSession->remoteConnection[ i ].used && !pSession->remoteConnection[ i ].m_migrated )
		{
			finished = false;
		}
	}

	float timeout = CNetCVars::Get().hostMigrationTimeout - (CryMatchMakingHostMigratedConnectionConfirmationWindow / 1000.0f);
	float timetaken = (g_time - pSession->hostMigrationInfo.m_startTime).GetSeconds();

	if (finished || (pTask->canceled) || (timetaken > timeout))
	{
		NetLog( "[Host Migration]: matchmaking host migration finished for session %i", pTask->session );

		for ( uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; ++i )
		{
			if ( pSession->remoteConnection[ i ].used )
			{
				if ( pSession->remoteConnection[ i ].m_migrated )
				{
					// This connection migrated so reset for next time
					NetLog( "[Host Migration]: client uid %i, session %i migrated successfully @ %fs", pSession->remoteConnection[ i ].uid.m_uid, pTask->session, timetaken );
					pSession->remoteConnection[ i ].m_migrated = false;
				}
				else
				{
					// Prune this connection
					if ( CNetCVars::Get().netPruneConnectionsAfterHostMigration != 0 )
					{
						NetLog( "[Host Migration]: client uid %i, session %i failed to migrate - pruning @ %fs", pSession->remoteConnection[ i ].uid.m_uid, pTask->session, timetaken );
						SessionDisconnectRemoteConnectionViaNub( pTask->session, i, eDC_FailedToMigrateToNewHost, "Failed to migrate player to new game", true );
					}
					else
					{
						NetLog( "[Host Migration]: client uid %i, session %i failed to migrate but pruning disabled @ %fs", pSession->remoteConnection[ i ].uid.m_uid, pTask->session, timetaken );
					}
				}
			}
		}

		pSession->hostMigrationInfo.m_matchMakingFinished = true;
		StopTaskRunning( mmTaskID );
	}
}


void CCryGameSpyMatchMaking::HostMigrationServerPeerJoinRoomCallback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void* pParam )
{
	STaskLink*						pLink = static_cast< STaskLink* >( pParam );

	if ( pLink )
	{
		STask*								pTask = pLink->pTask;

		if ( pTask )
		{
			if ( success )
			{
				SMigrateServerParam*	pMigrateServerParam = static_cast< SMigrateServerParam* >( pLink->pLobby->MemGetPtr( pTask->params[ MIGRATE_SERVER_PARAM ] ) );
				pMigrateServerParam->flags |= eMSF_FinishedCreate;
			}
			else
			{
				pLink->pMatchMaking->UpdateTaskError( static_cast<CryMatchMakingTaskID>(pTask - pLink->pMatchMaking->m_task), eCLE_ServerUnreachable );
			}
		}

		FreeTaskLinkFromGameSpy( pLink );
	}
}


void CCryGameSpyMatchMaking::ProcessHostMigrationFromServer( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	pPacket->StartRead();

	CryMatchMakingTaskID					serverMmTaskId = pPacket->ReadUINT32();
	uint32												numMigrations = pPacket->ReadUINT32();
	CrySessionHandle							localSessionIdx;
	SCryMatchMakingConnectionUID	mmCxUID = pPacket->GetFromConnectionUID();
	CryMatchMakingConnectionID		mmCxID = CryMatchMakingInvalidConnectionID;

	if ( FindConnectionFromUID( mmCxUID, &localSessionIdx, &mmCxID ) )
	{
		SSession*					pSession = &m_sessions[ localSessionIdx ];

		pSession->hostConnectionID = mmCxID;
		pSession->id.m_addr = pSession->remoteConnection[ mmCxID ].id;

		if (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST)
		{
			NetLog("[Host Migration]: ignoring session details from uid %u as I am already the server (sid %llX, uid %i, session %i)", mmCxUID.m_uid, pSession->localConnection.uid.m_sid, pSession->localConnection.uid.m_uid, localSessionIdx);
		}
		else
		{
			NetLog("[Host Migration]: matchmaking CLIENT received session details from uid %u - migrating to new GameSpy session (session %i)", mmCxUID.m_uid, localSessionIdx);
			if ( m_lobby )
			{
				TMemHdl mh = m_lobby->MemAlloc(sizeof(SCryLobbyRoomOwnerChanged));
				if (mh != TMemInvalidHdl)
				{
					SCryLobbyRoomOwnerChanged* pOwnerData = (SCryLobbyRoomOwnerChanged*)m_lobby->MemGetPtr(mh);

					pOwnerData->m_session = CreateGameSessionHandle(localSessionIdx, pSession->localConnection.uid);
					CNetwork::DecodeAddress(addr, &pOwnerData->m_ip, &pOwnerData->m_port);
					pOwnerData->m_ip = htonl( pOwnerData->m_ip );

					TO_GAME_FROM_LOBBY(&CCryGameSpyMatchMaking::DispatchRoomOwnerChangedEvent, this, mh);
				}
			}

			pSession->hostMigrationInfo.m_newHostAddress = addr;
			pSession->numMigrations = numMigrations;
			TO_GAME_FROM_LOBBY( &CCryGameSpyMatchMaking::HostMigrationClient, this, static_cast< SHostMigrationInfo * >( &pSession->hostMigrationInfo ), serverMmTaskId );
		}
	}
}


void CCryGameSpyMatchMaking::ProcessHostMigrationFromClient( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	uint32									bufferPos = 0;
	CryMatchMakingTaskID		hostTaskID = CryMatchMakingInvalidTaskID;

	pPacket->StartRead();
	hostTaskID = pPacket->ReadUINT32();

	CryLobbyConnectionID		c;

	if ( m_lobby->ConnectionFromAddress( &c, addr ) )
	{
		hostTaskID = FindTaskFromTaskTaskID( eT_SessionMigrateHostFinish, hostTaskID );
		if ( hostTaskID != CryMatchMakingInvalidTaskID )
		{
			STask*									pTask = &m_task[ hostTaskID ];
			SSession*								pSession = &m_sessions[ pTask->session ];

			for ( uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; i++ )
			{
				SSession::SRConnection*	connection = &pSession->remoteConnection[ i ];

				if ( connection->used && ( connection->connectionID == c ) )
				{
					float										timetaken = ( g_time - pSession->hostMigrationInfo.m_startTime ).GetSeconds();

					connection->m_migrated = true;
					NetLog( "[Host Migration]: matchmaking SERVER received CLIENT confirmation (session %i, connection %i, uid %i) @ %fs", pTask->session, i, connection->uid.m_uid, timetaken );
					return;
				}
			}
		}
	}

	SCryMatchMakingConnectionUID mmCxUID = pPacket->GetFromConnectionUID();
	NetLog("[Host Migration]: matchmaking SERVER received CLIENT confirmation for unknown connection (was uid %i)", mmCxUID.m_uid);
}


void CCryGameSpyMatchMaking::HostMigrationClient( SHostMigrationInfo* pHostMigrationInfo, CryMatchMakingTaskID serverMmTaskId )
{
	ECryLobbyError				error = eCLE_Success;
	CrySessionHandle			localSessionIdx = CrySessionInvalidHandle;
	SSession*							pSession = NULL;
	CryMatchMakingTaskID	mmTaskID;

	LOBBY_AUTO_LOCK;

	localSessionIdx = GetSessionHandleFromGameSessionHandle( pHostMigrationInfo->m_session );
	pSession = &m_sessions[ localSessionIdx ];
	pSession->localFlags &= ~CRYSESSION_LOCAL_FLAG_HOST;
	error = StartTask( eT_SessionMigrateHostClient, &mmTaskID, NULL, localSessionIdx, NULL, NULL );

	if ( error == eCLE_Success )
	{
		error = CreateTaskParam( mmTaskID, MIGRATE_CLIENT_PARAM, NULL, 1, sizeof ( SMigrateClientParam ) );

		if ( error == eCLE_Success )
		{
			STask*								pTask = &m_task[ mmTaskID ];
			SMigrateClientParam*	pParam = static_cast< SMigrateClientParam* >( m_lobby->MemGetPtr( pTask->params[ MIGRATE_SERVER_PARAM ] ) );

			pParam->serverMmTaskId = serverMmTaskId;
			pParam->flags = 0;
#if HOST_MIGRATION_SOAK_TEST_BREAK_DETECTION
			DetectHostMigrationTaskBreak(localSessionIdx, "HostMigrationClient()");
#endif
			pSession->hostMigrationInfo.m_taskID = pTask->lTaskID;

			NetLog( "[Host Migration]: matchmaking migrating session [%d] on the CLIENT", localSessionIdx );
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}

	if ( error != eCLE_Success )
	{
		NetLog( "[Host Migration]: CCryLANMatchMaking::HostMigrationClient(): session %d, error %d", localSessionIdx, error );

		m_hostMigration.Terminate( static_cast< SHostMigrationInfo_Private* >( pHostMigrationInfo ) );
	}
}


void CCryGameSpyMatchMaking::StartHostMigrationClient( CryMatchMakingTaskID mmTaskID )
{
	STask*										pTask = &m_task[ mmTaskID ];
	SSession*									pSession = &m_sessions[ pTask->session ];
	SSession::SRConnection*		pConnection = &pSession->remoteConnection[pSession->hostConnectionID];

	if (m_lobby->ConnectionGetState(pConnection->connectionID) == eCLCS_Connected)
	{
		SMigrateClientParam*			pMigrateClientParam = static_cast< SMigrateClientParam* >( m_lobby->MemGetPtr( pTask->params[ MIGRATE_CLIENT_PARAM ] ) );
		CCrySharedLobbyPacket			packet;
		const uint32							maxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT32Size;

		if ( packet.CreateWriteBuffer( maxBufferSize ) )
		{
			packet.StartWrite( eGameSpyPT_HostMigrationClient, true );
			packet.WriteUINT32( pMigrateClientParam->serverMmTaskId );
			if ( Send( mmTaskID, &packet, pTask->session, pSession->hostConnectionID ) == eSE_Ok )
			{
				NetLog("[Host Migration]: matchmaking CLIENT migrated to new session [%d] successfully", pTask->session);
				pSession->hostMigrationInfo.m_sessionMigrated = true;
				pSession->hostMigrationInfo.m_matchMakingFinished = true;
				pSession->hostMigrationInfo.m_newHostAddressValid = true;

				if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
				{
					CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

					CreateSessionURL( pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_sid, url );
					GetService()->GPSetLocationString( url.c_str() );
				}
			}
			else
			{
				UpdateTaskError( mmTaskID, eCLE_InternalError );
			}
		}
		else
		{
			UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
		}
	}
	else
	{
		NetLog("[Host Migration]: matchmaking CLIENT migrated to new session [%d] successfully, but host disconnected", pTask->session);
	}

	StopTaskRunning( mmTaskID );
}


bool CCryGameSpyMatchMaking::GetNewHostAddress( char* pAddress, SHostMigrationInfo* pHostMigrationInfo )
{
	CrySessionHandle			sessionIndex = GetSessionHandleFromGameSessionHandle( pHostMigrationInfo->m_session );
	SSession*							pSession = &m_sessions[ sessionIndex ];
	bool									success = pSession->hostMigrationInfo.m_newHostAddressValid;

	if ( success )
	{
		// Decode the address into an ip string
		if ( pSession->hostMigrationInfo.m_newHostAddress.GetPtr< TLocalNetAddress >() )
		{
			pSession->hostMigrationInfo.SetIsNewHost( true );
			NetLog( "[Host Migration]: becoming the new host..." );

			const SCryLobbyParameters& lobbyParams = m_lobby->GetLobbyParameters();
			uint16 port = lobbyParams.m_listenPort;
			sprintf_s(pAddress,HOST_MIGRATION_MAX_SERVER_NAME_SIZE,"%s:%d",LOCAL_CONNECTION_STRING,port);
		}
		else
		{
			pSession->hostMigrationInfo.SetIsNewHost( false );
			NetLog( "[Host Migration]: new host is uid %i", pSession->newHostUID.m_uid );
			CNetwork::DecodeAddress( pSession->hostMigrationInfo.m_newHostAddress, pAddress, false );
		}
	}

	return success;
}


void CCryGameSpyMatchMaking::HostMigrationInitialise( CrySessionHandle h, EDisconnectionCause cause )
{
	SSession*				pSession = &m_sessions[ h ];

	pSession->hostMigrationInfo.Reset();

	if ( pSession->newHostUID.m_uid == pSession->localConnection.uid.m_uid )
	{
		// New host is me so no need to be told the new host address on GameSpy.
		pSession->hostMigrationInfo.m_newHostAddress = TNetAddress( TLocalNetAddress() );
		pSession->hostMigrationInfo.m_newHostAddressValid = true;

		if ( cause == eDC_Timeout )
		{
			NetLog("[Host Migration]: becoming new host for session [%i] - pushing migration event to all connections", h );
			// Ensure migration event is 'pushed' to all known connections in case they've not yet detected the original server has gone
			const uint32		maxBufferSize = CryLobbyPacketHeaderSize;
			uint8						buffer[ maxBufferSize ];
			CCryLobbyPacket packet;

			packet.SetWriteBuffer( buffer, maxBufferSize );
			packet.StartWrite( eGameSpyPT_HostMigrationStart, true );
			SendToAll( CryMatchMakingInvalidTaskID, &packet, h, CryMatchMakingInvalidConnectionID );
		}
	}
}


void CCryGameSpyMatchMaking::HostMigrationFinalise(CrySessionHandle h)
{
	SSession* pSession = &m_sessions[h];
	HostMigrationReset(h);

	if ((pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) == 0)
	{
		MarkHostHintInformationDirty(h);
	}
}


void CCryGameSpyMatchMaking::StartSessionEnsureBestHost( CryMatchMakingTaskID mmTaskID )
{
	STask*											pTask = &m_task[ mmTaskID ];
	CrySessionHandle						localSessionIndex = pTask->session;
	SSession*										pSession = &m_sessions[ localSessionIndex ];

	// Ensure we're the host and we've not started the session
	if ( ( pSession->localFlags & ( CRYSESSION_LOCAL_FLAG_HOST | CRYSESSION_LOCAL_FLAG_STARTED ) ) == CRYSESSION_LOCAL_FLAG_HOST )
	{
		// Determine if host hinting has a better host
		SHostHintInformation newHost;
		CryMatchMakingConnectionID newHostID;
		if (FindConnectionFromSessionUID(pTask->session, pSession->newHostUID, &newHostID))
		{
			SSession::SRConnection* pConnection = &pSession->remoteConnection[newHostID];
			newHost = pConnection->hostHintInfo;
		}

		bool isBetterHost = false;
		if (SortNewHostPriorities_ComparatorHelper(newHost, pSession->hostHintInfo) < 0)
		{
			isBetterHost = true;
		}

		if (isBetterHost)
		{
			if ( HostMigrationInitiate( localSessionIndex, eDC_Unknown ) )
			{
				// I'm currently the host but there's a better host out there...
				const uint32								maxBufferSize = CryLobbyPacketHeaderSize;
				uint8												buffer[ maxBufferSize ];
				CCryLobbyPacket							packet;

				NetLog("[Host Migration]: local server is not best host for session [%i] - instructing clients to choose new host", pTask->session );
				packet.SetWriteBuffer( buffer, maxBufferSize );
				packet.StartWrite( eGameSpyPT_HostMigrationStart, true );

				SendToAll( mmTaskID, &packet, localSessionIndex, CryMatchMakingInvalidConnectionID );
				pSession->localFlags &= ~CRYSESSION_LOCAL_FLAG_HOST;
			}
			else
			{
				NetLog("[Host Migration]: local server is not best host for session [%i], but session is not migratable", pTask->session);
				UpdateTaskError( mmTaskID, eCLE_SessionNotMigratable );
			}
		}
		else
		{
			NetLog("[Host Migration]: local server is best host for session [%i]", pTask->session);
		}
	}
	else
	{
		if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_STARTED )
		{
			NetLog( "[Host Migration]: SessionEnsureBestHost() called after the session had started" );
		}
	}

	StopTaskRunning( mmTaskID );
}


void CCryGameSpyMatchMaking::ProcessHostMigrationStart( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	CrySessionHandle							localSessionIndex = CrySessionInvalidHandle;
	CryMatchMakingConnectionID		mmCxID = CryMatchMakingInvalidConnectionID;

	SCryMatchMakingConnectionUID	mmCxUID = pPacket->GetFromConnectionUID();

	if ( FindConnectionFromUID( mmCxUID, &localSessionIndex, &mmCxID ) )
	{
		NetLog( "[Host Migration]: received host migration push event for session [%i], uid %i", localSessionIndex, mmCxUID.m_uid );
		SSession*											pSession = &m_sessions[ localSessionIndex ];

		if ( !( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST ) )
		{
			if ( pSession->hostMigrationInfo.m_state == eHMS_Idle )
			{
				NetLog( "[Host Migration]: remote server is not best host for session [%i] - instructed to choose new host", localSessionIndex );
				HostMigrationInitiate( localSessionIndex, eDC_Unknown );
			}
			else
			{
				NetLog( "[Host Migration]: ignoring pushed migration event - already migrating session [%i]", localSessionIndex );
			}
		}
	}
	else
	{
		NetLog( "[Host Migration]: received push migration event from unknown host, uid %i", mmCxUID.m_uid );
	}
}


#endif


// Description:
//	 Create a task.
// Arguments:
//	 etask - task type
//	 pMmTaskID - return matchmaking task ID
//	 pLTaskID - return lobby task ID
//	 h - session handle
//	 cb - callback
//	 pCbArg - callback argument
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyMatchMaking::StartTask( ETask etask, CryMatchMakingTaskID* pMmTaskID, CryLobbyTaskID* pLTaskID, CrySessionHandle h, void* cb, void* pCbArg )
{
	CryMatchMakingTaskID	tmpMMTaskID;
	CryMatchMakingTaskID*	pUseMMTaskID = pMmTaskID ? pMmTaskID : &tmpMMTaskID;
	ECryLobbyError				error = CCryMatchMaking::StartTask( etask, false, pUseMMTaskID, pLTaskID, h, cb, pCbArg );

	if ( error == eCLE_Success )
	{
		ResetTask(*pUseMMTaskID);
	}

	return error;
}


void CCryGameSpyMatchMaking::ResetTask(CryMatchMakingTaskID mmTaskID)
{
	STask *pTask = &m_task[mmTaskID];
	pTask->newGameSpyObjects = 0;
	pTask->oldGameSpyObjects = 0;
	pTask->searching = false;
}


// Description:
//	 Start a task; must be called on network thread.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::StartTaskRunning( CryMatchMakingTaskID mmTaskID )
{
	LOBBY_AUTO_LOCK;

	STask*	pTask = &m_task[ mmTaskID ];

	if (pTask->used)
	{
#if ENABLE_CRYLOBBY_DEBUG_TESTS
		ECryLobbyError error;

		if (!m_lobby->DebugOKToStartTaskRunning(pTask->lTaskID))
		{
			return;
		}

		if (m_lobby->DebugGenerateError(pTask->lTaskID, error))
		{
			UpdateTaskError(mmTaskID, error);
			StopTaskRunning(mmTaskID);

			if ((pTask->startedTask == eT_SessionCreate) || (pTask->startedTask == eT_SessionJoin))
			{
				FreeSessionHandle(pTask->session);
			}

			if (pTask->startedTask == eT_SessionSearch)
			{
				m_search[pTask->session].used = false;
			}

			return;
		}
#endif

		pTask->running = true;

		switch ( pTask->startedTask )
		{
		case eT_SessionRegisterUserData:
		case eT_SessionGetUsers:
		case eT_SessionUpdateSlots:
		case eT_SessionMigrate:

			// Synchronous non-blocking.

			StopTaskRunning( mmTaskID );
			break;

		case eT_SessionStart:

			StartSessionStart( mmTaskID );
			break;

		case eT_SessionEnd:

			StartSessionEnd( mmTaskID );
			break;

		case eT_SessionCreate:

			StartSessionCreate( mmTaskID );
			break;

		case eT_SessionDelete:

			StartSessionDelete( mmTaskID );
			break;

#if !defined(DEDICATED_SERVER)
		case eT_SessionJoin:

			StartSessionJoin( mmTaskID );
			break;

		case eT_SessionSearch:

			StartSessionSearch( mmTaskID );
			break;

		case eT_SessionQuery:

			StartSessionQuery( mmTaskID );
			break;
#endif // !defined(DEDICATED_SERVER)

		case eT_SessionUpdate:

			StartSessionUpdate( mmTaskID );
			break;

		case eT_SessionEnsureCorrectStagingRoom:

			StartSessionEnsureCorrectStagingRoom( mmTaskID );
			break;

#if NETWORK_HOST_MIGRATION

		case eT_SessionEnsureBestHost:

			StartSessionEnsureBestHost( mmTaskID );
			break;

		case eT_SessionMigrateHostServer:

			StartHostMigrationServer( mmTaskID );
			break;

		case eT_SessionMigrateHostClient:

			StartHostMigrationClient( mmTaskID );
			break;

#endif

		case eT_SessionSetLocalUserData:

			StartSessionSetLocalUserData( mmTaskID );
			break;

		case eT_SessionSendRequestInfo:
			StartSessionSendRequestInfo(mmTaskID);
			break;

#if defined(DEDICATED_SERVER)
		case eT_SessionPropagatePassword:
			StartSessionPropagatePassword(mmTaskID);
			break;
#endif // defined(DEDICATED_SERVER)
		}
	}
}


// Description:
//	 Stop a task; must be called on network thread.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::StopTaskRunning( CryMatchMakingTaskID mmTaskID )
{
	assert( mmTaskID < MAX_MATCHMAKING_TASKS );

	STask*					pTask = &m_task[mmTaskID];

	if ( pTask->used )
	{
		StopTaskSearching( mmTaskID );
		FreeTaskLinkFromCryNetwork( pTask );
		pTask->running = false;
		TO_GAME_FROM_LOBBY( &CCryGameSpyMatchMaking::EndTask, this, mmTaskID );
	}
}


// Description:
//	 Handle task termination and invoke callback. Must be called on game thread.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::EndTask( CryMatchMakingTaskID mmTaskID )
{
	LOBBY_AUTO_LOCK;

	STask*					pTask = &m_task[ mmTaskID ];

	if ( pTask->used )
	{
		if ( pTask->cb )
		{
			switch ( pTask->startedTask )
			{
			case eT_SessionStart:
			case eT_SessionEnd:
			case eT_SessionDelete:
			case eT_SessionEnsureBestHost:

				( ( CryMatchmakingCallback )pTask->cb )( pTask->lTaskID, pTask->error, pTask->cbArg );
				break;

			case eT_SessionRegisterUserData:
			case eT_SessionSetLocalUserData:
			case eT_SessionUpdate:
			case eT_SessionUpdateSlots:

				// Synchronous non-blocking.

				( ( CryMatchmakingCallback )pTask->cb )( pTask->lTaskID, pTask->error, pTask->cbArg );
				break;

			case eT_SessionGetUsers:

				EndSessionGetUsers( mmTaskID );
				break;

			case eT_SessionCreate:

				EndSessionCreate( mmTaskID );

				( ( CryMatchmakingSessionCreateCallback )pTask->cb )(
					pTask->lTaskID,
					pTask->error,
					CreateGameSessionHandle( pTask->session, m_sessions[ pTask->session ].localConnection.uid ),
					pTask->cbArg );

				if (pTask->error == eCLE_Success)
				{
					InitialUserDataEvent( pTask->session );
				}

				break;

			case eT_SessionMigrate:

				( ( CryMatchmakingSessionCreateCallback )pTask->cb )(
					pTask->lTaskID,
					pTask->error,
					CreateGameSessionHandle( pTask->session, m_sessions[ pTask->session ].localConnection.uid ),
					pTask->cbArg );
				break;

#if !defined(DEDICATED_SERVER)
			case eT_SessionQuery:

				EndSessionQuery( mmTaskID );
				break;

			case eT_SessionSearch:

				EndSessionSearch( mmTaskID );
				break;
#endif // !defined(DEDICATED_SERVER)

			case eT_SessionSendRequestInfo:
				EndSessionSendRequestInfo(mmTaskID);
				break;

#if defined(DEDICATED_SERVER)
			case eT_SessionPropagatePassword:
				if (pTask->cb)
				{
					((CryMatchmakingCallback)pTask->cb)(pTask->lTaskID, pTask->error, pTask->cbArg);
				}
				break;
#endif // defined(DEDICATED_SERVER)

#if !defined(DEDICATED_SERVER)
			case eT_SessionJoin:
				SSession*					pSession = m_sessions + pTask->session;
				CrySessionHandle	gameSessionHandle = CreateGameSessionHandle( pTask->session, pSession->localConnection.uid );

				( ( CryMatchmakingSessionJoinCallback )pTask->cb )( pTask->lTaskID, pTask->error, gameSessionHandle, pSession->id.m_addr.m_useIP, ntohs( pSession->id.m_addr.m_usePort ), pTask->cbArg );

				if ( pTask->error == eCLE_Success )
				{
					InitialUserDataEvent( pTask->session );
				}

				break;
#endif // !defined(DEDICATED_SERVER)
			}
		}

		// Clean up still needed even if task cancellation nulled out the callback.

#if !defined(DEDICATED_SERVER)
		switch ( pTask->startedTask )
		{
		case eT_SessionSearch:
			if (pTask->session != CrySessionInvalidHandle)
			{
				m_search[ pTask->session ].used = false;
			}
			break;
		}
#endif // !defined(DEDICATED_SERVER)

		// Clear GameSpy specific task state so that base class tasks can use this slot
		ResetTask(mmTaskID);

		FreeTask( mmTaskID );

		if ( pTask->error != eCLE_Success )
		{
			CryLogAlways( "[Lobby] EndTask %d (%d) error %d", pTask->startedTask, pTask->subTask, pTask->error );
		}
	}
}


// Description:
//	 Clear a task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::ClearTask( CryMatchMakingTaskID mmTaskID )
{
	assert( mmTaskID < MAX_MATCHMAKING_TASKS );

	STask*					pTask = &m_task[mmTaskID];

	pTask->timer = CTimeValue();
	pTask->lTaskID = CryLobbyInvalidTaskID;
	pTask->error = eCLE_InternalError;
	pTask->returnTaskID = CryMatchMakingInvalidTaskID;
	pTask->startedTask = eT_InvalidTaskType;
	pTask->subTask = eT_InvalidTaskType;
	pTask->session = CrySessionInvalidHandle;
	pTask->cb = 0;
	pTask->cbArg = 0;
	pTask->session = CryLobbyInvalidSendID;
	pTask->sendStatus = eCLE_InternalError;
	pTask->used = false;
	pTask->running = false;
	pTask->timerStarted = false;
	pTask->canceled = false;
	pTask->pLink = NULL;
}


// Description:
//	 Start a session create task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::StartSessionCreate( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];

	if ( CreateTaskLink( mmTaskID ) )
	{
		SSession*					pSession = &m_sessions[ pTask->session ];
		EGameSpyObjectBit	objects = ( EGameSpyObjectBit )( eGSOB_GPConnection | eGSOB_Peer | eGSOB_CDKey );

		InitializeTaskGameSpyObjects( pTask, objects, 0 );
	}
}


// Description:
//	 Tick a session create task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::TickSessionCreate( CryMatchMakingTaskID mmTaskID )
{
	STask*										pTask = &m_task[ mmTaskID ];
	SSession*									pSession = &m_sessions[ pTask->session ];
	CCryGameSpyLobbyService*	pService = GetService();
	SSessionCreateParam*			pParam = ( SSessionCreateParam* )m_lobby->MemGetPtr( pTask->params[ CREATE_CONTROL_PARAM ] );

	if ( ( pTask->error == eCLE_Success ) && !pTask->canceled )
	{
		SCrySessionData*					pSessionData = ( SCrySessionData* )m_lobby->MemGetPtr( pTask->params[ CREATE_SESSION_DATA_PARAM ] );

		if ( !( pParam->flags & eSCTF_DoneSetSessionData ) )
		{
			SetSessionData( pTask->session, pSessionData );
			pParam->flags |= eSCTF_DoneSetSessionData;
		}

		if ( pService->HasPublicAddress() )
		{
			pParam->flags |= eSCTF_HavePublicAddress;
		}

		if ( pService->HasLocalUserName() )
		{
			pParam->flags |= eSCTF_HaveLocalUserName;
		}

		if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && !( pParam->flags & eSCTF_CreatedStagingRoom  ) )
		{
			uint32			maxPlayers = m_sessions[ pTask->session ].data.m_numPrivateSlots + m_sessions[ pTask->session ].data.m_numPublicSlots;

			if ( gEnv->IsDedicated() )
			{
				++maxPlayers;
			}

			GetService()->PeerCreateStagingRoom( m_sessions[ pTask->session ].data.m_name, maxPlayers, 0, SessionCreatePeerJoinRoomCallback, pTask->pLink );
			pParam->flags |= eSCTF_CreatedStagingRoom;
		}

		if ( ( ( pParam->flags & eSCTF_JoinedStagingRoom ) || ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) == 0 ) ) && ( ( pParam->flags & ( eSCTF_HavePublicAddress | eSCTF_HaveLocalUserName ) ) == ( eSCTF_HavePublicAddress | eSCTF_HaveLocalUserName ) ) )
		{
			SSession*				pSession = m_sessions + pTask->session;

			strncpy( pSession->localConnection.name, GetService()->GetLocalUserName(), CRYLOBBY_USER_NAME_LENGTH );
			pSession->localConnection.name[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = '\0';
			pSession->localConnection.userID = *static_cast< SCryGameSpyUserID* >( GetService()->GetUserID( 0 ).userID.get() );
			pSession->id.m_sid = CreateSessionID();
			pSession->id.m_addr = CreateRemoteConnectionID(0, 0, GetPublicIP(), GetPublicPort(), GetPrivateIP(), GetPrivatePort());

			if ( pParam->flags & eSCTF_JoinedStagingRoom )
			{
				PushUserData( pTask->session );
			}

			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

				CreateSessionURL( pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_sid, url );
				pService->GPSetLocationString( url.c_str() );
			}

			pTask->error = eCLE_Success;
			StopTaskRunning( mmTaskID );
		}
	}
	else
	{
		if ( pParam->flags & eSCTF_CreatedStagingRoom )
		{
			GetService()->PeerLeaveStagingRoom( NULL, NULL );
		}

		FreeSessionHandle( pTask->session );
	}
}


// Description:
//	 Start a session update task.
// Arguments:
//	 mmTaskID - matchmaking task ID.
void CCryGameSpyMatchMaking::StartSessionUpdate( CryMatchMakingTaskID mmTaskID )
{
	STask*							pTask = &m_task[ mmTaskID ];
	SSession*						pSession = &m_sessions[ pTask->session ];

	if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
	{
		InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer );
	}
}


void CCryGameSpyMatchMaking::TickSessionUpdate( CryMatchMakingTaskID mmTaskID )
{
	STask*								pTask = &m_task[ mmTaskID ];
	SCrySessionUserData*	pData = ( SCrySessionUserData* )m_lobby->MemGetPtr( pTask->params[ UPDATE_SESSION_USER_DATA_PARAM ] );
	SSession*							pSession = &m_sessions[ pTask->session ];

	SetSessionUserData( pTask->session, pData, pTask->numParams[ UPDATE_SESSION_USER_DATA_PARAM ] );

	if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
	{
		PushUserData( pTask->session );
	}

	StopTaskRunning( mmTaskID );
}


void CCryGameSpyMatchMaking::PushUserData( CrySessionHandle localSessionIndex )
{
	SSession*				pSession = &m_sessions[ localSessionIndex ];
	uint32					maxPlayers = pSession->data.m_numPrivateSlots + pSession->data.m_numPublicSlots;

	if ( gEnv->IsDedicated() )
	{
		++maxPlayers;
	}

	GetService()->PeerStateChanged();
	GetService()->PeerSetStagingRoomMaxPlayers( maxPlayers );
}


void CCryGameSpyMatchMaking::QueryKey( qr2_key_type type, int key, int index, qr2_buffer_t buffer )
{
	char					strVal[ 128 ];
	bool					haveValue = false;

	switch ( type )
	{
	case key_server:

		haveValue = GetServerKey( key, sizeof ( strVal ), strVal );
		break;

	case key_player:

		// Not implemented.
		break;

	case key_team:

		// Not implemented.
		break;
	}

	if ( haveValue )
	{
		qr2_buffer_add( buffer, strVal );
	}
}


bool CCryGameSpyMatchMaking::GetServerKey( int key, size_t bufSize, char* pBuf )
{
	bool									result = true;
	SConfigurationParams	neededInfo[ 1 ];
	CrySessionHandle			localSessionIndex = GetCurrentHostedNetNubSessionHandle();
	SSession*							pSession = NULL;
	uint8									userDataIdx = KEY_USERDATA_IDX_UNUSED;

	if ( localSessionIndex != CrySessionInvalidHandle )
	{
		pSession = &m_sessions[ localSessionIndex ];

		if ( m_stringKeyToNumericKey[ key ] != KEY_USERDATA_IDX_UNUSED )
		{
			sprintf_s( pBuf, bufSize, "%s", pSession->stringKeys[ key ] );
		}
		else
		{
			userDataIdx = m_keyUserDataIdx[ key ];

			switch ( userDataIdx )
			{
			case KEY_USERDATA_IDX_UNUSED:

				result = false;
				break;

			case KEY_USERDATA_IDX_INTERNAL:

				switch ( key )
				{
				case eCGSK_HostNameKey:

					snprintf( pBuf, bufSize, "%s", pSession->data.m_name );
					break;

				case eCGSK_GameModeKey:

					snprintf( pBuf, bufSize, k_gameModeNames[ eGM_OpenPlaying ] );
					break;

				case eCGSK_GameNameKey:

					neededInfo[ 0 ].m_fourCCID = CLCC_GAMESPY_TITLE;
					m_lobby->GetConfigurationInformation( neededInfo, 1 );
					snprintf( pBuf, bufSize, "%s", ( const char* )neededInfo[ 0 ].m_pData );
					break;

				case eCGSK_NumPlayersKey:

					snprintf( pBuf, bufSize, "%u", pSession->numFilledSlots );
					break;

				case eCGSK_MaxPlayersKey:

					snprintf( pBuf, bufSize, "%u", pSession->data.m_numPrivateSlots + pSession->data.m_numPublicSlots );
					break;

				case eCGSK_PasswordRequiredKey:

					{
						uint32			value = 0;
						ICVar*			pPasswordCVar = gEnv->pConsole->GetCVar( "sv_password" );

						if ( pPasswordCVar )
						{
							const char*	pPassword = pPasswordCVar->GetString();

							if ( pPassword && *pPassword )
							{
								value = 1;
							}
						}

						snprintf( pBuf, bufSize, "%u", value );
					}
					break;

				case eCGSK_SessionIDKey:

					EncodeU64( pSession->id.m_sid, pBuf );
					break;

				case eCGSK_NumPrivateSlotsKey:

					EncodeU32( pSession->data.m_numPrivateSlots, pBuf );
					break;

				case eCGSK_NumPublicSlotsKey:

					EncodeU32( pSession->data.m_numPublicSlots, pBuf );
					break;

				case eCGSK_RankedKey:

					snprintf( pBuf, bufSize, "%u", pSession->data.m_ranked );
					break;

				case eCGSK_CreateFlagsKey:

					EncodeU32( pSession->createFlags, pBuf );
					break;

				case eCGSK_NumMigrationsKey:

					EncodeU32( pSession->numMigrations, pBuf );
					break;

				case eCGSK_DedicatedKey:

					snprintf( pBuf, bufSize, "%u", gEnv->IsDedicated() ? 1 : 0 );
					break;

				default:

					result = false;
					break;
				}

				break;

			default:

				switch ( m_registeredUserData.data[ userDataIdx ].m_type )
				{
				case eCLUDT_Int64:
					EncodeS64( pSession->data.m_data[ userDataIdx ].m_int64, pBuf );
					break;

				case eCLUDT_Int32:
					EncodeS32( pSession->data.m_data[ userDataIdx ].m_int32, pBuf );
					break;

				case eCLUDT_Int16:
					EncodeS16( pSession->data.m_data[ userDataIdx ].m_int16, pBuf );
					break;

				case eCLUDT_Int8:
					EncodeS8( pSession->data.m_data[ userDataIdx ].m_int8, pBuf );
					break;

				case eCLUDT_Float64:

					snprintf( pBuf, bufSize, "%f", pSession->data.m_data[ userDataIdx ].m_f64 );
					break;

				case eCLUDT_Float32:

					snprintf( pBuf, bufSize, "%f", pSession->data.m_data[ userDataIdx ].m_f32 );
					break;

				case eCLUDT_Int64NoEndianSwap:
					EncodeU64( pSession->data.m_data[ userDataIdx ].m_int64, pBuf );
					break;
				}
			}
		}
	}

	return result;
}


// Description:
//	 Start a session query task.
// Arguments:
//	 mmTaskID - matchmaking task ID.
void CCryGameSpyMatchMaking::StartSessionQuery( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSession*				pSession = &m_sessions[ pTask->session ];

	if ( CreateTaskLink( mmTaskID ) )
	{
		SSessionQueryParam*	pParam = ( SSessionQueryParam* )m_lobby->MemGetPtr( pTask->params[ QUERY_PARAM ] );

		if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST )
		{
			ICVar*			pPasswordCVar = gEnv->pConsole->GetCVar( "sv_password" );

			if ( pPasswordCVar )
			{
				const char*	pPassword = pPasswordCVar->GetString();

				if ( pPassword && *pPassword )
				{
					pParam->requirePassword = true;
				}
			}

			pParam->finished = true;
			StopTaskRunning( mmTaskID );
		}
		else
		{
			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer );
			}
		}
	}
}


// Description:
//	 Tick a session create task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::TickSessionQuery( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSession*				pSession = &m_sessions[ pTask->session ];
	SSessionQueryParam*	pParam = ( SSessionQueryParam* )m_lobby->MemGetPtr( pTask->params[ QUERY_PARAM ] );
	
	if ( !pParam->started )
	{
		// Do not try to get session data from GameSpy here!

		const uint32					maxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT8Size;
		CCrySharedLobbyPacket packet;

		if ( packet.CreateWriteBuffer( maxBufferSize ) )
		{
			packet.StartWrite( eGameSpyPT_MM_RequestServerData, true );
			packet.WriteUINT8( mmTaskID );
			Send( CryMatchMakingInvalidTaskID, &packet, pTask->session, pSession->hostConnectionID );
		}
		else
		{
			UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
		}

		pParam->started = true;
	}

	if ( pParam->finished )
	{
		StopTaskRunning( mmTaskID );
	}
}


void CCryGameSpyMatchMaking::EndSessionQuery( CryMatchMakingTaskID mmTaskID )
{
	STask*									pTask = &m_task[ mmTaskID ];
	SSessionQueryParam*			pParam = ( SSessionQueryParam* )m_lobby->MemGetPtr( pTask->params[ QUERY_PARAM ] );
	SSession*								pSession = &m_sessions[ pTask->session ];
	SCrySessionSearchResult	result;
	SCryGameSpySessionID*		pID = new SCryGameSpySessionID();

	if ( pID )
	{
		result.m_data.m_data = pSession->data.m_data;
		result.m_data.m_numData = pSession->data.m_numData;
		result.m_data.m_numPublicSlots = pSession->data.m_numPublicSlots;
		result.m_data.m_numPrivateSlots = pSession->data.m_numPrivateSlots;
		strncpy( result.m_data.m_name, pSession->data.m_name, MAX_SESSION_NAME_LENGTH );
		result.m_data.m_ranked = pSession->data.m_ranked;
		pID->m_sid = pSession->id.m_sid;
		pID->m_addr = pSession->id.m_addr;
		result.m_id = pID;
		result.m_numFilledSlots = pSession->numFilledSlots;

		ICryFriends*	pFriends = GetService()->GetFriends();

		if ( pFriends )
		{
			CCryGameSpyFriends*	pGameSpyFriends = static_cast< CCryGameSpyFriends* >( pFriends );
			CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

			CreateSessionURL( pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_sid, url );
			result.m_numFriends = pGameSpyFriends->GetFriendCountInSession( url.c_str() );
		}
		else
		{
			result.m_numFriends = 0;
		}
		
		result.m_ping = 0;
		result.m_flags = 0;

		if ( pParam->requirePassword )
		{
			result.m_flags |= eCSSRF_RequirePassword;
		}

		( ( CryMatchmakingSessionQueryCallback )pTask->cb )( pTask->lTaskID, pTask->error, &result, pTask->cbArg );
	}
	else
	{
		( ( CryMatchmakingSessionQueryCallback )pTask->cb )( pTask->lTaskID, eCLE_OutOfMemory, NULL, pTask->cbArg );
	}
}


void CCryGameSpyMatchMaking::SendServerData( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	// Read client task ID and session ID for which data is requested, because we need to
	// include them in the reply to enable the client to match the reply with the task.

	pPacket->StartRead();
	uint8 taskID = pPacket->ReadUINT8();
	uint64 sessionID = pPacket->GetFromSessionSID();

	for ( uint32 i = 0; i < MAX_MATCHMAKING_SESSIONS; ++i )
	{
		SSession* pSession = &m_sessions[ i ];
		if ( ((pSession->localFlags & (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) == (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) && ( pSession->id.m_sid == sessionID ) )
		{
			uint32 bufferSize = CalculateServerDataSize( i );
			CCrySharedLobbyPacket packet;

			if ( packet.CreateWriteBuffer( bufferSize ) )
			{
				uint32			flags = 0;

				packet.StartWrite( eGameSpyPT_MM_ServerData, true );
				packet.WriteUINT8( taskID );
				packet.WriteUINT32( pSession->numFilledSlots );
				packet.WriteUINT32( pSession->data.m_numPublicSlots );
				packet.WriteUINT32( pSession->data.m_numPrivateSlots );
				packet.WriteData( pSession->data.m_name, MAX_SESSION_NAME_LENGTH );

				if ( pSession->data.m_ranked )
				{
					flags |= eSDF_Ranked;
				}

				ICVar*			pPasswordCVar = gEnv->pConsole->GetCVar( "sv_password" );

				if ( pPasswordCVar )
				{
					const char*	pPassword = pPasswordCVar->GetString();

					if ( pPassword && *pPassword )
					{
						flags |= eSDF_RequirePassword;
					}
				}

				packet.WriteUINT32( flags );

				for ( uint32 j = 0; j < m_numServerQueryUserDataKeys; ++j )
				{
					packet.WriteCryLobbyUserData( &pSession->data.m_data[ m_keyUserDataIdx[ m_serverQueryUserDataKeys[ j ] ] ] );
				}

				Send( CryMatchMakingInvalidTaskID, &packet, i, addr );
				packet.FreeWriteBuffer();
			}

			break;
		}
	}
}


void CCryGameSpyMatchMaking::ProcessServerData( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
#if !defined(DEDICATED_SERVER)
	pPacket->StartRead();
	CryMatchMakingTaskID taskID = pPacket->ReadUINT8();
	uint64 sessionID = pPacket->GetFromSessionSID();

	if ( taskID < MAX_MATCHMAKING_TASKS )
	{
		STask* pTask = &m_task[ taskID ];

		if ( pTask->used && pTask->running && ( pTask->startedTask == eT_SessionQuery ) )
		{
			SSession* pSession = &m_sessions[ pTask->session ];

			if ( pSession->id.m_sid == sessionID )
			{
				CryLobbyConnectionID	senderConnectionID;
				m_lobby->ConnectionFromAddress( &senderConnectionID, addr );
				if ( senderConnectionID == pSession->remoteConnection[ pSession->hostConnectionID ].connectionID )
				{
					SSessionQueryParam*	pParam = ( SSessionQueryParam* )m_lobby->MemGetPtr( pTask->params[ QUERY_PARAM ] );
					uint32							flags;

					pSession->numFilledSlots = pPacket->ReadUINT32();
					pSession->data.m_numPublicSlots = pPacket->ReadUINT32();
					pSession->data.m_numPrivateSlots = pPacket->ReadUINT32();
					pPacket->ReadData( pSession->data.m_name, MAX_SESSION_NAME_LENGTH );
					flags = pPacket->ReadUINT32();
					pSession->data.m_ranked = ( ( flags & eSDF_Ranked ) != 0 );
					pParam->requirePassword = ( ( flags & eSDF_RequirePassword ) != 0 );

					for ( uint32 j = 0; j < m_numServerQueryUserDataKeys; ++j )
					{
						pPacket->ReadCryLobbyUserData( &pSession->data.m_data[ m_keyUserDataIdx[ m_serverQueryUserDataKeys[ j ] ] ] );
					}

					pParam->finished = true;
				}
			}
		}
	}
#endif
}


uint32 CCryGameSpyMatchMaking::CalculateServerDataSize( CrySessionHandle localSessionIdx )
{
	size_t size =
		CryLobbyPacketHeaderSize +
		CryLobbyPacketUINT8Size +					// Requesters task id
		CryLobbyPacketUINT32Size +				// Num filled slots
		CryLobbyPacketUINT32Size +				// Num public slots
		CryLobbyPacketUINT32Size +				// Num private slots
		MAX_SESSION_NAME_LENGTH +					// Session name
		CryLobbyPacketUINT32Size;						// Flags

	SSession* pSession = &m_sessions[ localSessionIdx ];

	for ( uint8 i = 0; i < m_numServerQueryUserDataKeys; ++i )
	{
		uint8				keyUserDataIdx = m_keyUserDataIdx[ m_serverQueryUserDataKeys[ i ] ];

		if ( keyUserDataIdx != KEY_USERDATA_IDX_INTERNAL )
		{
			switch ( pSession->data.m_data[ keyUserDataIdx ].m_type )
			{
			case eCLUDT_Int64:
			case eCLUDT_Float64:
			case eCLUDT_Int64NoEndianSwap:
				size += CryLobbyPacketUINT64Size;
				break;
			case eCLUDT_Int32:
			case eCLUDT_Float32:
				size += CryLobbyPacketUINT32Size;
				break;
			case eCLUDT_Int16:
				size += CryLobbyPacketUINT16Size;
				break;
			case eCLUDT_Int8:
				size += CryLobbyPacketUINT8Size;
				break;
			}
		}
	}

	return size;
}


// Description:
//	 End a session create task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::EndSessionCreate( CryMatchMakingTaskID	mmTaskID )
{
	STask*						pTask = &m_task[mmTaskID];
	SSession*					pSession = m_sessions + pTask->session;
	const char*				pUserName = GetService()->GetLocalUserName();
	size_t						userNameLen = strlen( pUserName );

	if ( pTask->error == eCLE_Success )
	{
		pSession->localConnection.uid = CreateConnectionUID( pTask->session );
		strncpy( pSession->localConnection.name, pUserName, userNameLen + 1 );
	}
	else
	{
		FreeSessionHandle( pTask->session );
	}
}


// Description:
//	 Create a session ID.
// Return:
//	 The session ID.
uint64 CCryGameSpyMatchMaking::CreateSessionID()
{
	uint64					sessionID = 0;

	sessionID |= ( uint64 )GetService()->GetProfileID() << GAMESPY_SESSION_ID_PROFILE_SHIFT;
	sessionID |= time( NULL ) & GAMESPY_SESSION_ID_TIME_MASK;

	return sessionID;
}


// Description:
//	 Start a session join task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::StartSessionJoin( CryMatchMakingTaskID mmTaskID )
{
	STask*								pTask = &m_task[ mmTaskID ];
	SSession*							pSession = &m_sessions[ pTask->session ];
	uint32 cookie;

	if (pSession->id.m_addr.m_cookie == 0)
	{
		cookie = CreateNATCookie(&pSession->id.m_addr);
	}
	else
	{
		cookie = pSession->id.m_addr.m_cookie;
	}

	pTask->StartTimer();
	StartSubTask(eT_SessionJoinFindUseAddress, mmTaskID);
	StartFindUseAddress(&pSession->id.m_addr, cookie, 0, true);
}

void CCryGameSpyMatchMaking::NATNegotiateProgressCallback(NegotiateState state, void* pUserData)
{
	NetLog( "[Lobby] Processing NAT negotiation as client" );
	NetLog( "[Lobby]     Cookie:             %x", ( uint32 )pUserData );

	switch ( state )
	{
	case ns_initack:
		NetLog( "[Lobby]     Request acknowledged by NAT negotiation server" );
		break;
	case ns_connectping:
		NetLog( "[Lobby]     Direct negotiation with partner started" );
		break;
	}
}

void CCryGameSpyMatchMaking::NATNegotiateCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in* pRemoteAddr, void* pUserData)
{
	CNetwork* pNetwork = CNetwork::Get();

	NetLog( "[Lobby] Finishing NAT negotiation as client" );
	NetLog( "[Lobby]     Cookie:             %x", ( uint32 )pUserData );
	NetLog( "[Lobby]     Result:             %d", result );

	if (pNetwork)
	{
		CCryLobby* pLobby = (CCryLobby*)pNetwork->GetLobby();

		if (pLobby)
		{
			ICryLobbyService* pService = pLobby->GetLobbyService(eCLS_Online);

			if (pService)
			{
				CCryGameSpyMatchMaking* pThis = (CCryGameSpyMatchMaking*)pService->GetMatchMaking();

				if (pThis)
				{
					uint32 i;
					uint32 j;
					uint32 cookie = (uint32)pUserData;

					for ( i = 0; i < MAX_MATCHMAKING_SESSIONS; ++i )
					{
						SSession* pSession = &pThis->m_sessions[i];

						if (pSession->localFlags & CRYSESSION_LOCAL_FLAG_USED)
						{
							if ( CompleteNATNegotiation( result, pLobby, cookie, &pSession->id.m_addr, NULL, pRemoteAddr ) )
							{
								break;
							}

							for ( j = 0; j < MAX_LOBBY_CONNECTIONS; ++j )
							{
								SSession::SRConnection* pConnection = &pSession->remoteConnection[j];

								if (pConnection->used && (pConnection->connectionID == CryLobbyInvalidConnectionID))
								{
									if ( CompleteNATNegotiation( result, pLobby, cookie, &pConnection->id, pConnection, pRemoteAddr ) )
									{
										if ( pConnection->id.m_state == eGSNT_Succeeded )
										{
											pConnection->connectionID = pThis->GetLobbyConnectionFromRemoteConnectionID( pConnection->id );
											pLobby->ConnectionAddRef( pConnection->connectionID );

#if USE_CRYLOBBY_GAMESPY_VOIP
											CCryGameSpyVoice*				pVoice = static_cast< CCryGameSpyVoice* >( pService->GetVoice() );

											if ( pVoice )
											{
												SCryGameSpyUserID*	pUserID = new	SCryGameSpyUserID( pConnection->m_profileID );

												if ( pUserID && !(pConnection->m_isDedicated))
												{
													pVoice->LateSetUserLobbyConnection( pConnection->connectionID, pUserID );
												}
											}
#endif
										}

										break;
									}
								}
							}

							if ( j < MAX_LOBBY_CONNECTIONS )
							{
								break;
							}
						}
					}

					if ( i == MAX_MATCHMAKING_SESSIONS )
					{
						for ( i = 0; i < MAX_GAMESPY_SEARCHES; ++i )
						{
							if ( pThis->m_search[ i ].used )
							{
								for ( j = 0; j < pThis->m_search[ i ].numServers; ++j )
								{
									if ( CompleteNATNegotiation( result, pLobby, cookie, &static_cast< SCryGameSpySessionID* >( pThis->m_search[ i ].servers[ j ].result.m_id.get() )->m_addr, NULL, pRemoteAddr ) )
									{
										break;
									}
								}

								if ( j < pThis->m_search[ i ].numServers )
								{
									break;
								}
							}
						}

						if ( i == MAX_GAMESPY_SEARCHES )
						{
							for ( i = 0; i < MAX_MATCHMAKING_TASKS; ++i )
							{
								STask* pTask = &pThis->m_task[i];

								if (pTask->used && (pTask->subTask == eT_SessionSendRequestInfoNAT))
								{
									SCryGameSpySessionID* pSessionID = (SCryGameSpySessionID*)pThis->m_lobby->MemGetPtr(pTask->params[TDM_SEND_REQUEST_INFO_SESSIONID]);

									if (CompleteNATNegotiation(result, pLobby, cookie, &pSessionID->m_addr, NULL, pRemoteAddr))
									{
										break;
									}
								}
							}

							if (i == MAX_MATCHMAKING_TASKS)
							{
								NetLog( "[Lobby]     Unknown NAT negotiation" );
							}
						}
					}
				}
			}
		}
	}
}

bool CCryGameSpyMatchMaking::CompleteNATNegotiation( NegotiateResult nnResult, CCryLobby* pLobby, uint32 cookie, SCryGameSpyRemoteConnectionID* pAddr, SSession::SRConnection* pConnection, struct sockaddr_in* pRemoteAddr )
{
	bool			result = false;

	if ( ( pAddr->m_state == eGSNT_Started ) && ( pAddr->m_cookie == cookie ) )
	{
		NetLog( "[Lobby]     Partner:            %s %d.%d.%d.%d:%d",
			pConnection ? "other client" : "server",
			( ( uint8* )&pAddr->m_publicIP )[ 0 ], ( ( uint8* )&pAddr->m_publicIP )[ 1 ],
			( ( uint8* )&pAddr->m_publicIP )[ 2 ], ( ( uint8* )&pAddr->m_publicIP )[ 3 ], ntohs( pAddr->m_publicPort ) );

		if ( nnResult == nr_success )
		{
			pAddr->m_state = eGSNT_Succeeded;
			pAddr->m_useIP = pRemoteAddr->sin_addr.s_addr;
			pAddr->m_usePort = pRemoteAddr->sin_port;

			NetLog( "[Lobby]     NAT negotiation succeeded" );
			NetLog( "[Lobby]     Negotiated address: %d.%d.%d.%d:%d",
				( ( uint8* )&pAddr->m_useIP )[ 0 ], ( ( uint8* )&pAddr->m_useIP )[ 1 ],
				( ( uint8* )&pAddr->m_useIP )[ 2 ], ( ( uint8* )&pAddr->m_useIP )[ 3 ], ntohs( pAddr->m_usePort ) );
		}
		else
		{
			NetLog( "[Lobby]     NAT negotiation failed" ); 
			NetLog( "[Lobby]     Error:              %d", nnResult ); 

			pAddr->m_state = eGSNT_Failed;
		}

		result = true;
	}

	return result;
}

uint32 CCryGameSpyMatchMaking::CreateNATCookie(SCryGameSpyRemoteConnectionID* pAddr)
{
	uint32 res = (gEnv->pTimer->GetAsyncTime().GetValue())&0xffffffff;

	res ^= pAddr->m_publicIP;
	res ^= pAddr->m_publicPort;

	return res;
}

void CCryGameSpyMatchMaking::StartFindUseAddress(SCryGameSpyRemoteConnectionID* pAddr, uint32 cookie, int clientIndex, bool serverJoin)
{
	NetLog( "[Lobby] Starting NAT negotiation as client" );
	NetLog( "[Lobby]     Cookie:             %x", cookie );
	NetLog( "[Lobby]     Partner:            %s %d.%d.%d.%d:%d",
		serverJoin ? "server" : "other client",
		( ( uint8* )&pAddr->m_publicIP )[ 0 ], ( ( uint8* )&pAddr->m_publicIP )[ 1 ],
		( ( uint8* )&pAddr->m_publicIP )[ 2 ], ( ( uint8* )&pAddr->m_publicIP )[ 3 ], ntohs( pAddr->m_publicPort ) );

	if (pAddr->m_state == eGSNT_NotStarted)
	{
		if ( ( pAddr->m_publicIP != 0 ) && ( GetPublicIP() == pAddr->m_publicIP ) )
		{
			if ( ( pAddr->m_privateIP != 0 ) && ( pAddr->m_privatePort != 0 ) )
			{
				pAddr->m_state = eGSNT_Succeeded;
				pAddr->m_useIP = pAddr->m_privateIP;
				pAddr->m_usePort = pAddr->m_privatePort;

				NetLog("[Lobby]     Partner behind same NAT and private IP known, NAT negotiation not needed" );
			}
			else
			{
				NetLog("[Lobby]     Partner behind same NAT but private IP unknown, attempting NAT negotiation" );
			}
		}

		if ( pAddr->m_state == eGSNT_NotStarted )
		{
			pAddr->m_cookie = cookie;

			if ( serverJoin && ( ( pAddr->m_publicIP != 0 ) && ( pAddr->m_publicPort != 0 ) ) )
			{
				// If we are trying to connect to the server we need to send our cookie to the server.
				// If we are trying to connect to another client the other client got the cookie the same way we did from the server.
				GetService()->PeerSendNatNegotiateCookie(pAddr->m_publicIP, ntohs(pAddr->m_publicPort), cookie);

				NetLog("[Lobby]     Sent cookie to server" );
			}

			IDatagramSocketPtr pSocket = m_lobby->GetInternalSocket(m_serviceType);

			if (pSocket)
			{
				NegotiateError error = NNBeginNegotiationWithSocket(pSocket->GetSysSocket(), cookie, clientIndex, NATNegotiateProgressCallback, NATNegotiateCompletedCallback, (void*)pAddr->m_cookie);

				if (error == ne_noerror)
				{
					pAddr->m_state = eGSNT_Started;
					NetLog("[Lobby]     NAT negotiation started successfully" );
				}
				else
				{
					NetLog( "[Lobby]     NAT negotiation failed to start" );
					NetLog( "[Lobby]     Error:              %d", error ); 
					pAddr->m_state = eGSNT_Failed;
				}
			}
			else
			{
				NetLog( "[Lobby]     NAT negotiation failed to start with no socket" );
				pAddr->m_state = eGSNT_Failed;
			}
		}
	}
	else
	{
		if ( pAddr->m_state == eGSNT_Started )
		{
			NetLog( "[Lobby]     NAT negotiation already started", cookie );
		}
		else
		{
			if ( pAddr->m_state == eGSNT_Succeeded )
			{
				NetLog( "[Lobby]     NAT negotiation already succeeded" );
				NetLog( "[Lobby]     Negotiated address: %d.%d.%d.%d:%d",
					( ( uint8* )&pAddr->m_useIP )[ 0 ], ( ( uint8* )&pAddr->m_useIP )[ 1 ],
					( ( uint8* )&pAddr->m_useIP )[ 2 ], ( ( uint8* )&pAddr->m_useIP )[ 3 ], ntohs( pAddr->m_usePort ) );
			}
			else
			{
				NetLog( "[Lobby]     NAT negotiation already failed" );
			}
		}
	}
}

void CCryGameSpyMatchMaking::TickSessionJoinFindUseAddress(CryMatchMakingTaskID mmTaskID)
{
	STask* pTask = &m_task[mmTaskID];
	SSession* pSession = &m_sessions[pTask->session];

	EGSNatState state = pSession->id.m_addr.m_state; 

	// Ensure NAT negotiation sub task respects parent task timeout
	if (pTask->GetTimer() > CryMatchMakingConnectionTimeOut)
	{
		NetLog("[AndyC]: NAT neg timed out %lld", pTask->GetTimer());
		state = eGSNT_Failed;
	}

	switch (state)
	{
	case eGSNT_Started:
		break;

	case eGSNT_Succeeded:
		StartSessionRequestJoin(mmTaskID);
		break;

	case eGSNT_Failed:
		UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
		FreeSessionHandle(pTask->session);
		StopTaskRunning(mmTaskID);
		break;
	}
}

void CCryGameSpyMatchMaking::StartSessionRequestJoin( CryMatchMakingTaskID mmTaskID )
{
#if !defined(DEDICATED_SERVER)
	STask*								pTask = &m_task[ mmTaskID ];
	SSession*							pSession = &m_sessions[ pTask->session ];
	CCrySharedLobbyPacket	packet;

	CCryLobby* pLobby = static_cast<CCryLobby*>(gEnv->pNetwork->GetLobby());
	uint8 maxLen = 32;

	if (pLobby != NULL)
	{
		SConfigurationParams neededInfo[1] = { CLCC_MATCHMAKING_SESSION_PASSWORD_MAX_LENGTH, {NULL} };
		pLobby->GetConfigurationInformation(neededInfo, 1);
		maxLen = neededInfo[0].m_8;
	}

	const uint32					MaxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT8Size + (CryLobbyPacketUINT32Size * 2) + (CryLobbyPacketUINT16Size * 2) + CryLobbyPacketUINT8Size + CRYLOBBY_USER_NAME_LENGTH + CRYLOBBY_USER_DATA_SIZE_IN_BYTES + CryLobbyPacketUINT32Size + maxLen;

	TMemHdl h = m_lobby->MemAlloc(maxLen);
	char* pBuffer = static_cast<char*>(m_lobby->MemGetPtr(h));
	CRY_ASSERT(pBuffer != NULL);
	if (pBuffer != NULL)
	{
		memset(pBuffer, 0, maxLen);
		if (gEnv->pConsole != NULL)
		{
			ICVar* pPass = gEnv->pConsole->GetCVar("sv_password");
			strncpy(pBuffer, pPass->GetString(), maxLen);
		}
		StartSubTask(eT_SessionJoin, mmTaskID);

		if ( CreateTaskLink( mmTaskID ) )
		{
			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer ); // Not sure if peer disconnect invalidates SBservers from last search.
			}

			if ( packet.CreateWriteBuffer( MaxBufferSize ) )
			{
				strncpy( pSession->localConnection.name, GetService()->GetLocalUserName(), CRYLOBBY_USER_NAME_LENGTH );
				pSession->localConnection.name[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = '\0';
				pSession->localConnection.userID = *static_cast< SCryGameSpyUserID* >( GetService()->GetUserID( 0 ).userID.get() );

				NetLog("[Lobby] Request join session %llX", pSession->id.m_sid);

				packet.StartWrite( eGameSpyPT_SessionRequestJoin, true );
				packet.WriteUINT8( mmTaskID );
				packet.WriteData(pBuffer, maxLen);
				packet.WriteUINT32( GetPublicIP() );
				packet.WriteUINT16( GetPublicPort() );
				packet.WriteUINT32( GetPrivateIP() );
				packet.WriteUINT16( GetPrivatePort() );
				packet.WriteUINT8( pSession->localConnection.numUsers );
				packet.WriteString( pSession->localConnection.name, CRYLOBBY_USER_NAME_LENGTH );
				packet.WriteData( pSession->localConnection.userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
				packet.WriteUINT32( pSession->localConnection.userID.m_profileID );

				if ( Send( mmTaskID, &packet, pTask->session, TNetAddress( SIPv4Addr( ntohl( pSession->id.m_addr.m_useIP ), ntohs( pSession->id.m_addr.m_usePort ) ) ) ) != eSE_Ok )
				{
					UpdateTaskError( mmTaskID, eCLE_ConnectionFailed );
				}
			}
			else
			{
				UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
			}

			m_lobby->MemFree(h);
		}
	}
	else
	{
		UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
	}
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::TickSessionJoin( CryMatchMakingTaskID mmTaskID )
{
#if !defined(DEDICATED_SERVER)
	STask*										pTask = &m_task[ mmTaskID ];
	SSession*									pSession = m_sessions + pTask->session;
	SSessionJoinParam*				pParam = ( SSessionJoinParam* )( m_lobby->MemGetPtr( pTask->params[ JOIN_PARAM ] ) );

	if ( ( pTask->error == eCLE_Success ) && ( !pTask->canceled ) )
	{
		if ( pParam->finishedCryNetworkJoin )
		{
			StopTaskRunning( mmTaskID );
			SessionUserDataEvent( eCLSE_SessionUserJoin, pTask->session, pSession->hostConnectionID );

			if ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB )
			{
				CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

				CreateSessionURL( pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_sid, url );
				GetService()->GPSetLocationString( url.c_str() );
			}
		}
		else if ( pTask->GetTimer() > CryMatchMakingConnectionTimeOut )
		{
			UpdateTaskError( mmTaskID, eCLE_ConnectionFailed );
		}
	}
	
	if ( pTask->error != eCLE_Success )
	{
		SessionDisconnectRemoteConnectionViaNub( pTask->session, CryMatchMakingInvalidConnectionID, eDC_UserRequested, "Session deleted", true );
		SessionUserDataEvent( eCLSE_SessionUserLeave, pTask->session, CryMatchMakingInvalidConnectionID );

		for ( uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; ++i )
		{
			SSession::SRConnection* pConnection = &pSession->remoteConnection[ i ];

			if ( pConnection->used )
			{
				FreeRemoteConnection( pTask->session, i );
			}
		}

		FreeSessionHandle( pTask->session );
	}
#endif // !defined(DEDICATED_SERVER)
}


// Description:
//	 Start a session search task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::StartSessionSearch( CryMatchMakingTaskID mmTaskID )
{
	StartSubTask(eT_SessionSearchWaitStart, mmTaskID);
}

void CCryGameSpyMatchMaking::TickSessionSearchWaitStart(CryMatchMakingTaskID mmTaskID)
{
	STask* pTask = &m_task[ mmTaskID ];

	if ((pTask->error == eCLE_Success) && !pTask->canceled)
	{
		ECryLobbyError error = CreateSessionSearchHandle(&pTask->session);

		if (error == eCLE_Success)
		{
			if (CreateTaskLink(mmTaskID))
			{
				InitializeTaskGameSpyObjects(pTask, eGSOB_Peer, 0);
				StartSubTask(eT_SessionSearch, mmTaskID);
			}
			else
			{
				m_search[pTask->session].used = false;
			}
		}
	}

	if ((pTask->error != eCLE_Success) || pTask->canceled)
	{
		StopTaskRunning(mmTaskID);
	}
}

// Description:
//	 Tick a session search task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::TickSessionSearch( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSearch*				pSearch = &m_search[ pTask->session ];
	uint32					numPending = 0;

	if ( ( pTask->error == eCLE_Success ) && !( pTask->searching || pSearch->finished ) )
	{
		SCrySessionSearchParam*	pParam = static_cast< SCrySessionSearchParam* >( m_lobby->MemGetPtr( pTask->params[ SEARCH_PARAM_GAME_PARAM ] ) );
		SCrySessionSearchData*	pData = static_cast< SCrySessionSearchData* >( m_lobby->MemGetPtr( pTask->params[ SEARCH_PARAM_GAME_PARAM_DATA ] ) );
		std::map< CryLobbyUserDataID, std::vector< SCrySessionSearchData > >	searchDataByID;
		std::map< CryLobbyUserDataID, std::vector< SCrySessionSearchData > >::iterator itSearchDataByID;
		std::vector< SCrySessionSearchData >::iterator itSearchData;
		std::vector< CryLobbyUserDataID >	idsToRemove;
		CryFixedStringT< GAMESPY_SEARCH_FILTER_AVAILABLE_LEN >	filter;
		CryFixedStringT< GAMESPY_SEARCH_FILTER_AVAILABLE_LEN >	subFilter;
		CryFixedStringT< GAMESPY_SEARCH_FILTER_AVAILABLE_LEN >	subSubFilter;
		const char*							pLogic;
		uint32									seachDataIdx;

		for ( seachDataIdx = 0; seachDataIdx < pParam->m_numData; ++seachDataIdx )
		{
			searchDataByID[ pData[ seachDataIdx ].m_data.m_id ].push_back( pData[ seachDataIdx ] );
		}

		std::map< CryLobbyUserDataID, std::vector< SCrySessionSearchData > >::iterator	searchDataByIDEnd = searchDataByID.end();

		for ( itSearchDataByID = searchDataByID.begin(); ( itSearchDataByID != searchDataByIDEnd ) && ( pTask->error == eCLE_Success ); ++itSearchDataByID )
		{
			subFilter.clear();

			if ( itSearchDataByID->second.size() > 1 )
			{
				if ( !filter.empty() )
				{
					subFilter = " AND (";
				}
				else
				{
					subFilter = "(";
				}

				bool bHasSubFilter = false;
				for ( itSearchData = itSearchDataByID->second.begin(); ( itSearchData != itSearchDataByID->second.end() ) && ( pTask->error == eCLE_Success ); ++itSearchData )
				{
					if ( bHasSubFilter )
					{
						pLogic = " OR ";
					}
					else
					{
						pLogic = "";
					}

					if ( itSearchData->m_operator == eCSSO_Equal )
					{
						FormatSearchTerm( pLogic, &*itSearchData, subSubFilter );

						if ( !subSubFilter.empty() && ( subFilter.length() + subSubFilter.length() + 2 < subFilter.MAX_SIZE ) )
						{
							subFilter += subSubFilter;
							bHasSubFilter = true;
						}
						else
						{
							break;
						}
					}
					else
					{
						UpdateTaskError( mmTaskID, eCLE_InvalidParam );
					}
				}

				if ( itSearchData == itSearchDataByID->second.end() )
				{
					subFilter += ")";
				}
				else
				{
					subFilter.clear();
				}
			}
			else
			{
				if ( !filter.empty() )
				{
					pLogic = " AND ";
				}
				else
				{
					pLogic = "";
				}

				FormatSearchTerm( pLogic, &itSearchDataByID->second[ 0 ], subFilter );
			}

			if ( !subFilter.empty() && ( filter.length() + subFilter.length() + 1 < filter.MAX_SIZE ) )
			{
				filter += subFilter;
				idsToRemove.push_back( itSearchDataByID->first ); // Callback won't need filters for this datum
			}
		}

		if ( pTask->error == eCLE_Success )
		{
			// Reconstruct filters to be applied in callback from the ones that didn't fit in the query string.

			for ( std::vector< CryLobbyUserDataID >::iterator itIdsToRemove = idsToRemove.begin(); itIdsToRemove != idsToRemove.end(); ++itIdsToRemove )
			{
				searchDataByID.erase( *itIdsToRemove );
			}

			searchDataByIDEnd = searchDataByID.end();

			for ( itSearchDataByID = searchDataByID.begin(), seachDataIdx = 0; itSearchDataByID != searchDataByIDEnd; ++itSearchDataByID, ++seachDataIdx )
			{
				for ( itSearchData = itSearchDataByID->second.begin(); itSearchData != itSearchDataByID->second.end(); ++itSearchData, ++seachDataIdx )
				{
					pData[ seachDataIdx ] = *itSearchData;
				}
			}

			pParam->m_numData = seachDataIdx;

			if(StartTaskSearching( mmTaskID, m_serverSearchQueryKeys, m_numServerSearchQueryKeys, filter.c_str(), &SessionSearchPeerListingGamesCallback, pTask->pLink ) == eCLE_NotConnected)
			{
				UpdateTaskError(mmTaskID, eCLE_InvalidRequest);
			}
		}
	}

#if GAMESPY_ENABLE_SESSION_PING
	if ( ( pTask->error == eCLE_Success ) && ( pTask->searching || pSearch->finished ) && CNetCVars::Get().gamespySessionPing )
	{
		for ( uint32 i = 0; i < pSearch->numServers; ++i )
		{
			SSearch::SServer*			pServer = &pSearch->servers[ i ];
			SCryGameSpySessionID*	pId = static_cast< SCryGameSpySessionID* >( pServer->result.m_id.get() );

			if ( pId->m_addr.m_state == eGSNT_NotStarted )
			{
				StartFindUseAddress( &pId->m_addr, CreateNATCookie( &pId->m_addr ), 0, true );
			}
			else
			{
				if ( ( pId->m_addr.m_state == eGSNT_Failed ) || ( ( pId->m_addr.m_state == eGSNT_Succeeded ) && ( ( pServer->sessionPingState == eSPS_GotPongInvalidSession ) || ( ( pServer->sessionPingState == eSPS_SentPing ) && ( ( g_time - pServer->firstPingSentTime ).GetMilliSeconds() > CNetCVars::Get().gamespySearchPingTimeOut ) ) ) ) )
				{
					--pSearch->numServers;
					--pSearch->numExpectedServers;

					if ( i == pSearch->numServers )
					{
						FreeSessionSearchServer( pServer );
					}
					else
					{
						MoveSessionSearchServer( pServer, &pSearch->servers[ pSearch->numServers ] );
					}
				}
				else
				{
					if ( ( pId->m_addr.m_state == eGSNT_Succeeded ) && ( ( pServer->sessionPingState == eSPS_NotStarted ) || ( ( pServer->sessionPingState == eSPS_SentPing ) && ( ( g_time - pServer->lastPingSentTime ).GetMilliSeconds() > CNetCVars::Get().gamespySearchPingRetry ) ) ) )
					{
						const uint32					MaxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT8Size + CryLobbyPacketUINT64Size;
						CCrySharedLobbyPacket	packet;

						if ( packet.CreateWriteBuffer( MaxBufferSize ) )
						{
							packet.StartWrite( eGameSpyPT_SessionPing, false );
							packet.WriteUINT8( mmTaskID );
							packet.WriteUINT64( pId->m_sid );

							if ( Send( mmTaskID, &packet, CrySessionInvalidHandle, TNetAddress( SIPv4Addr( ntohl( pId->m_addr.m_useIP ), ntohs( pId->m_addr.m_usePort ) ) ) ) == eSE_Ok )
							{
								pServer->lastPingSentTime = g_time;

								if ( pServer->sessionPingState == eSPS_NotStarted )
								{
									pServer->sessionPingState = eSPS_SentPing;
									pServer->firstPingSentTime = g_time;
								}
							}
						}
					}
				}
			}
		}
	}
#endif

	if ( ( pTask->error == eCLE_Success ) )			// We can start returning results immediately (assuming we have some valid ones)
	{
		int allDoneCount=0;
		for ( uint32 i = 0; i < pSearch->numServers; ++i )
		{
			SSearch::SServer*			pServer = &pSearch->servers[ i ];

			if ( pServer->sessionPingState == eSPS_ReportedToGame)
			{
				allDoneCount++;
			}
			if ( pServer->sessionPingState == eSPS_GotPongValidSession )
			{
				if ( pSearch->servers[ i ].dedicated && ( pSearch->servers[ i ].mode == eGM_OpenStaging ) )
				{
					--pSearch->servers[ i ].result.m_numFilledSlots;
				}

				pServer->sessionPingState = eSPS_ReportedToGame;

				if ( pTask->cb )
				{
					TO_GAME_FROM_LOBBY(
						&CCryGameSpyMatchMaking::ReturnSessionSearch,
						this,
						( CryMatchmakingSessionSearchCallback )pTask->cb,
						pTask->lTaskID,
						eCLE_SuccessContinue,
						&pServer->result,
						pTask->cbArg );
				}
			}
			else if ( pServer->sessionPingState != eSPS_ReportedToGame )
			{
				++numPending;
			}
		}
		if ((allDoneCount == pSearch->numExpectedServers))
		{
			pSearch->finished=true;
		}
	}

	if ( ( pTask->error != eCLE_Success ) || pSearch->finished )
	{
		if ( pTask->searching )
		{
			StopTaskSearching( mmTaskID );
			FreeTaskLinkFromGameSpy( pTask->pLink );
		}
	}

	if ( ( pTask->error != eCLE_Success ) || ( pSearch->finished && ( numPending == 0 ) ) )
	{
		StopTaskRunning( mmTaskID );
	}
}

void CCryGameSpyMatchMaking::FormatSearchTerm( const char* pLogic, const SCrySessionSearchData* pData, CryFixedStringT< GAMESPY_SEARCH_FILTER_AVAILABLE_LEN >& filter )
{
	const char*							pOp;
	const char*							pOpen;
	const char*							pClose;
	bool										serverSideInts = false;

	pOpen = "(";
	pClose = ")";

	switch ( pData->m_operator )
	{
	case eCSSO_Equal:
		pOp = "=";
		serverSideInts = true;
		break;
	case eCSSO_NotEqual:
		pOp = "!=";
		serverSideInts = true;
		break;
	case eCSSO_LessThan:
		pOp = "<";
		break;
	case eCSSO_LessThanEqual:
		pOp = "<=";
		break;
	case eCSSO_GreaterThan:
		pOp = ">";
		break;
	case eCSSO_GreaterThanEqual:
		pOp = ">=";
		break;
	case eCSSO_BitwiseAndNotEqualZero:
		CRY_ASSERT_MESSAGE( ( pData->m_data.m_type != eCLUDT_Float64 ) && ( pData->m_data.m_type != eCLUDT_Float32 ), "Bitwise operator not valid for floating point user data" );
		pOpen = "((";
		pOp = "&";
		pClose = ")!=0)";
		break;
	}

	char	buffer[ 32 ];

	switch ( pData->m_data.m_id )
	{
	case eCGSK_RegionKey:
		filter.Format( "%s%s%s%s%i%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
		break;

	default:
		switch ( pData->m_data.m_type )
		{
		case eCLUDT_Int64:
			if ( serverSideInts )
			{
				EncodeS64( pData->m_data.m_int64, buffer );
				filter.Format( "%s%s%s%s'%s'%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
			}
			break;
		case eCLUDT_Int32:
			if ( serverSideInts )
			{
				EncodeS32( pData->m_data.m_int32, buffer );
				filter.Format( "%s%s%s%s'%s'%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
			}
			break;
		case eCLUDT_Int16:
			if ( serverSideInts )
			{
				EncodeS16( pData->m_data.m_int16, buffer );
				filter.Format( "%s%s%s%s'%s'%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
			}
			break;
		case eCLUDT_Int8:
			if ( serverSideInts )
			{
				EncodeS8( pData->m_data.m_int8, buffer );
				filter.Format( "%s%s%s%s'%s'%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
			}
			break;
		case eCLUDT_Float64:
			filter.Format( "%s%s%s%s%f%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, pData->m_data.m_f64, pClose );
			break;
		case eCLUDT_Float32:
			filter.Format( "%s%s%s%s%f%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, pData->m_data.m_f32, pClose );
			break;
		case eCLUDT_Int64NoEndianSwap:
			if ( serverSideInts )
			{
				EncodeS64( SwapEndianValue( pData->m_data.m_int64, eBigEndian ), buffer );
				filter.Format( "%s%s%s%s'%s'%s", pLogic, pOpen, qr2_registered_key_list[ pData->m_data.m_id ], pOp, buffer, pClose );
			}
			break;
		}
		break;
	}
}

// Description:
//	 Return a session search result.
// Arguments:
//	 cb - callback
//	 taskID - lobby task ID
//	 error - error
//	 pResult - result
//	 pCbArg - callback argument
void CCryGameSpyMatchMaking::ReturnSessionSearch( CryMatchmakingSessionSearchCallback cb, CryLobbyTaskID taskID, ECryLobbyError error, SCrySessionSearchResult* pResult, void* pCbArg )
{
	cb( taskID, error, pResult, pCbArg );
}


// Description:
//	 End a session search task.
// Arguments:
//	 mmTaskID - matchmaking task ID
void CCryGameSpyMatchMaking::EndSessionSearch( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[mmTaskID];
	SSearch*				pSearch = m_search + pTask->session;

	( ( CryMatchmakingSessionSearchCallback )pTask->cb )(
		pTask->lTaskID,
		pTask->error,
		0,
		pTask->cbArg );
}


// Description:
//	 Start a session delete task.
// Arguments:
//	 mmTaskID - match making task ID
void CCryGameSpyMatchMaking::StartSessionDelete( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSession*				pSession = &m_sessions[ pTask->session ];

#if NETWORK_HOST_MIGRATION
	// Since we're deleting this session, terminate any host migration
	if (pSession->hostMigrationInfo.m_state != eHMS_Idle)
	{
		m_hostMigration.Terminate(&pSession->hostMigrationInfo);
	}
#endif
	pSession->createFlags &= ~CRYSESSION_CREATE_FLAG_MIGRATABLE;

	if(pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB)
	{
		GetService()->GPSetLocationString("");
	}

	// Disconnect our local connection
	SessionDisconnectRemoteConnectionViaNub( pTask->session, CryMatchMakingInvalidConnectionID, eDC_UserRequested, "Session deleted", true );
	SessionUserDataEvent( eCLSE_SessionUserLeave, pTask->session, CryMatchMakingInvalidConnectionID );

	// Free any remaining remote connections
	for ( uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; i++ )
	{
		SSession::SRConnection* pConnection = &pSession->remoteConnection[ i ];

		if ( pConnection->used )
		{
			FreeRemoteConnection( pTask->session, i );
		}
	}

	FreeSessionHandle( pTask->session );
	StopTaskRunning( mmTaskID );
}


// Description:
//	 End a SessionGetUsers task.
// Arguments:
//	 mmTaskID - match making task ID
void CCryGameSpyMatchMaking::EndSessionGetUsers( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];

	if ( pTask->error == eCLE_Success )
	{
		SCryUserInfoResult		temp;
		SCryGameSpyUserID*		pID = new SCryGameSpyUserID();

		int						a;

		// Glue in local user(s)

		if ( pID )
		{
			temp.m_conID = m_sessions[ pTask->session ].localConnection.uid;
			temp.m_isDedicated = false;										// Only applicable to dedicated clients (dedicated server doesn't use this information)
			strncpy( temp.m_userName,m_sessions[ pTask->session ].localConnection.name,CRYLOBBY_USER_NAME_LENGTH );
			temp.m_userName[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = 0;
			memcpy( temp.m_userData, m_sessions[pTask->session].localConnection.userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
			pID->m_profileID = m_sessions[ pTask->session ].localConnection.userID.m_profileID;
			temp.m_userID = pID;
			( ( CryMatchmakingSessionGetUsersCallback )pTask->cb )( pTask->lTaskID, eCLE_SuccessContinue, &temp, pTask->cbArg );
		}
		
		for ( a = 0; a < MAX_LOBBY_CONNECTIONS; a++ )
		{
			if ( m_sessions[ pTask->session ].remoteConnection[ a ].used )
			{
				pID = new SCryGameSpyUserID();

				if ( pID )
				{
					temp.m_conID = m_sessions[ pTask->session ].remoteConnection[ a ].uid;
					temp.m_isDedicated = m_sessions[ pTask->session ].remoteConnection[ a ].m_isDedicated;
					strncpy( temp.m_userName,m_sessions[ pTask->session ].remoteConnection[ a ].name, CRYLOBBY_USER_NAME_LENGTH );
					temp.m_userName[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = 0;
					memcpy( temp.m_userData, m_sessions[ pTask->session ].remoteConnection[ a ].userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
					pID->m_profileID = m_sessions[ pTask->session ].remoteConnection[ a ].m_profileID;
					temp.m_userID = pID;
					( ( CryMatchmakingSessionGetUsersCallback )pTask->cb )( pTask->lTaskID, eCLE_SuccessContinue, &temp, pTask->cbArg );
				}
			}
		}

		( ( CryMatchmakingSessionGetUsersCallback )pTask->cb )( pTask->lTaskID, eCLE_Success, NULL, pTask->cbArg );
	}
	else
	{
		( ( CryMatchmakingSessionGetUsersCallback )pTask->cb )( pTask->lTaskID, pTask->error, NULL, pTask->cbArg );
	}
}


// Description:
//	 Start a session set local user data task.
// Arguments:
//	 mmTaskID - matchmaking task ID.
void CCryGameSpyMatchMaking::StartSessionSetLocalUserData( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[mmTaskID];
	const uint32			MaxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT16Size + CRYLOBBY_USER_DATA_SIZE_IN_BYTES;
	CCrySharedLobbyPacket	packet;

	SessionUserDataEvent(eCLSE_SessionUserUpdate, pTask->session, CryMatchMakingInvalidConnectionID);

	if (packet.CreateWriteBuffer(MaxBufferSize))
	{
		SSession* pSession = &m_sessions[pTask->session];
		SSession::SLConnection* pLConnection = &pSession->localConnection;

		packet.StartWrite(eGameSpyPT_UserData, true);
		packet.WriteUINT16(pLConnection->uid.m_uid);
		packet.WriteData(pLConnection->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);

		if (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST)
		{
			SendToAll(CryMatchMakingInvalidTaskID, &packet, pTask->session, CryMatchMakingInvalidConnectionID);
		}
		else
		{
			Send(CryMatchMakingInvalidTaskID, &packet, pTask->session, pSession->hostConnectionID);
		}

		packet.FreeWriteBuffer();
	}
	else
	{
		UpdateTaskError(mmTaskID, eCLE_OutOfMemory);
	}

	StopTaskRunning(mmTaskID);
}


// Description:
//	 Process a local user data packet.
// Arguments:
//	 addr - sender
//	 pPacket - packet
void CCryGameSpyMatchMaking::ProcessLocalUserData( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	CrySessionHandle				h;
	SCryMatchMakingConnectionUID	uid;
	CryMatchMakingConnectionID		id;

	pPacket->StartRead();
	uid = pPacket->GetFromConnectionUID();
	uid.m_uid = pPacket->ReadUINT16();

	if ( FindConnectionFromUID( uid, &h, &id ) )
	{
		SSession*						pSession = &m_sessions[ h ];
		SSession::SRConnection*			pRConnection = &pSession->remoteConnection[ id ];

		pPacket->ReadData( pRConnection->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
		SessionUserDataEvent( eCLSE_SessionUserUpdate, h, id );

		if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST )
		{
			SendToAll( CryMatchMakingInvalidTaskID, pPacket, h, id );
		}
	}
}


// Description:
//	 Create a session handle.
// Arguments:
//	 pLocalSessionIndex - return session handle
//	 host - is local machine host?
//	 createFlags - flags
//	 numUsers - number of local users
// Return:
//	 Error code
ECryLobbyError CCryGameSpyMatchMaking::CreateSessionHandle( CrySessionHandle* pLocalSessionIndex, bool host, uint32 createFlags, int numUsers )
{
	ECryLobbyError			error = CCryMatchMaking::CreateSessionHandle( pLocalSessionIndex, host, createFlags, numUsers );

	if ( error == eCLE_Success )
	{
		SSession*				pSession = &m_sessions[ *pLocalSessionIndex ];

		pSession->data.m_data = pSession->userData;
		pSession->numFilledSlots = numUsers;
		pSession->specialFlags = 0;
		pSession->data.m_numData = m_registeredUserData.num;
		pSession->id.m_sid = 0;
		pSession->id.m_addr.m_cookie = 0;
		pSession->id.m_addr.m_state = eGSNT_NotStarted;
		pSession->hostConnectionID = CryMatchMakingInvalidConnectionID;
		pSession->numMigrations = 0;
		pSession->stringKeys.clear();
		memset(pSession->localConnection.userData, 0, sizeof(pSession->localConnection.userData));

		for ( uint32 j = 0; j < m_registeredUserData.num; ++j )
		{
			pSession->data.m_data[ j ] = m_registeredUserData.data[ j ];
		}
	}

	return error;
}


// Description:
//	 Create a session search handle.
// Arguments:
//	 pSearchHandle - return session handle
// Return:
//	 Error code
ECryLobbyError CCryGameSpyMatchMaking::CreateSessionSearchHandle( CrySessionHandle* pSearchHandle )
{
	CrySessionHandle result = 0;

	if (!m_search[result].used)
	{
		FreeSessionSearch( &m_search[result] );
		m_search[result].used = true;
		*pSearchHandle = result;
		return eCLE_Success;
	}

	return eCLE_TooManyTasks;
};


// Description:
//	 Set session data.
// Arguments:
//	 localSessionIndex - session handle
//	 pData - session data
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyMatchMaking::SetSessionData( CrySessionHandle localSessionIndex, SCrySessionData* pData )
{
	ECryLobbyError			result;

	if ( m_sessions[ localSessionIndex ].localFlags & CRYSESSION_LOCAL_FLAG_USED )
	{
		m_sessions[ localSessionIndex ].data.m_numPublicSlots = pData->m_numPublicSlots;
		m_sessions[ localSessionIndex ].data.m_numPrivateSlots = pData->m_numPrivateSlots;
		strcpy( m_sessions[ localSessionIndex ].data.m_name, pData->m_name );
		m_sessions[ localSessionIndex ].data.m_ranked = pData->m_ranked;

		result = SetSessionUserData( localSessionIndex, pData->m_data, pData->m_numData );
	}
	else
	{
		result = eCLE_InvalidSession;
	}

	return result;
}


// Description:
//	 Set session user data.
// Arguments:
//	 localSessionIndex - session handle
//	 pData - session user data
//	 numData - user data count
// Return:
//	 Error code.
ECryLobbyError CCryGameSpyMatchMaking::SetSessionUserData( CrySessionHandle localSessionIndex, SCrySessionUserData* pData, uint32 numData )
{
	ECryLobbyError				result = eCLE_InvalidSession;
	SSession*							pSession = &m_sessions[ localSessionIndex ];
	SConfigurationParams	params[ eCGSK_NumGameReservedKeys ];
	SCryLobbyUserDataStringParam	paramsData[ eCGSK_NumGameReservedKeys ];
	uint32								paramCount = 0;

	if ( ( pSession->localFlags & ( CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST ) ) == ( CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST ) )
	{
		result = eCLE_Success;

		for ( uint32 i = 0; i < numData; ++i )
		{
			uint32					j = m_keyUserDataIdx[ pData[ i ].m_id ];

			if ( j < m_registeredUserData.num )
			{
				if ( pData[ i ].m_id == eCGSK_HostProfileKey )
				{
					pSession->data.m_data[ j ].m_int32 = GetService()->GetProfileID();
				}
				else
				{
					if ( KEY_IS_CUSTOM_NUMERIC( pData[ i ].m_id ) )
					{						
						if ( m_numericKeyToStringKey[ pData[ i ].m_id ] != KEY_USERDATA_IDX_UNUSED )
						{
							params[ paramCount ].m_fourCCID = CLCC_GAMESPY_KEYSTRINGVALUE;
							params[ paramCount ].m_pData = &paramsData[ paramCount ];
							paramsData[ paramCount ].id = pData[ i ].m_id;
							paramsData[ paramCount ].value = pData[ i ].m_int32;
							++paramCount;
						}
					}

					if ( pData[ i ].m_type == m_registeredUserData.data[ j ].m_type )
					{
						pSession->data.m_data[ j ] = pData[ i ];
					}
					else
					{
						result = eCLE_UserDataMissMatch;
					}
				}
			}
			else
			{
				result = eCLE_UserDataNotRegistered;
			}
		}

		if ( paramCount )
		{
			m_lobby->GetConfigurationInformation( params, paramCount );

			for ( uint32 i = 0; i < paramCount; ++i )
			{
				pSession->stringKeys[ m_numericKeyToStringKey[ paramsData[ i ].id ] ] = static_cast< const char* >( params[ i ].m_pData );
			}
		}
	}

	return result;
}


// Description:
//	 Called when a peerCreateStagingRoom completes.
// Arguments:
//	 peer - PEER
//	 success - was the peerCreateStagingRoom successful?
//	 result - type of result
//	 roomType - always StagingRoom for this callback
//	 pParam - pointer to STaskLink
void CCryGameSpyMatchMaking::SessionCreatePeerJoinRoomCallback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void* pParam )
{
	STaskLink*				pLink = static_cast< STaskLink* >( pParam );

	if ( !pLink )
	{
		return;
	}

	if ( pLink->pTask )
	{
		if ( success )
		{
			SSessionCreateParam*	pParam = ( SSessionCreateParam* )pLink->pLobby->MemGetPtr( pLink->pTask->params[ CREATE_CONTROL_PARAM ] );

			pParam->flags |= eSCTF_JoinedStagingRoom;
		}
		else
		{
			pLink->pTask->error = eCLE_ConnectionFailed;
		}
	}

	FreeTaskLinkFromGameSpy( pLink );
}

// Description:
//	 Called when state of session search changes.
// Arguments:
//	 peer - PEER
//	 success - was the search successful?
//	 pName - name of game
//	 server - server
//	 staging - true if not yet in gameplay
//	 msg - type of update
//	 progress - percentage of servers added
//	 pParam - user data
void CCryGameSpyMatchMaking::SessionSearchPeerListingGamesCallback( PEER peer, PEERBool success, const gsi_char* pName, SBServer server, PEERBool staging, int msg, int progress, void* pParam )
{
	STaskLink*				pLink = static_cast< STaskLink* >( pParam );

	if ( !pLink )
	{
		return;
	}

	STask*						pTask = pLink->pTask;

	if ( pTask )
	{
		CCryGameSpyMatchMaking*		pMatchMaking = pLink->pMatchMaking;
		SSearch*									pSearch = &pMatchMaking->m_search[ pTask->session ];

		if (!success)
		{
			pSearch->finished=true;
			pMatchMaking->UpdateTaskError(static_cast<CryMatchMakingTaskID>(pTask - pMatchMaking->m_task), eCLE_InternalError);
			return;
		}

		switch ( msg )
		{
		case PEER_CLEAR:

			for ( int i = 0; i < pSearch->numServers; ++i )
			{
				FreeSessionSearchServer( &pSearch->servers[ i ] );
			}

			pSearch->numExpectedServers = MAX_GAMESPY_SEARCH_SERVERS;
			pSearch->numServersAdded = 0;
			pSearch->numServers = 0;
			break;

		case PEER_COMPLETE:
			
			pSearch->numExpectedServers = pSearch->numServersAdded;

//			pSearch->finished = true;			// We used to set search finished at this point, but thats not the best thing to do, since it will cut off future
																			//results from the server. Instead we now push "new" results back to the game as and when we get them. Instead
																			//of queuing them all up and returning them in one block.
			break;

		case PEER_ADD:
			pSearch->numServersAdded++;
			//FALL THROUGH INTENDED
		case PEER_UPDATE:
		case PEER_REMOVE:

			CCryGameSpyLobbyService*	pLobbyService = pLink->pLobbyService;
			CCryLobby*								pLobby = pMatchMaking->m_lobby;
			int32											removeOldRecords[ 2 ] = { -1, -1 };
			uint32										removeOldRecordCount = 0;
			SSearch::SServer					tmpServer;
			SCrySessionSearchParam*		pSearchParam;
			SCrySessionSearchData*		pSearchData;
			uint32										maxResults;

			if ( ( pTask->numParams[ SEARCH_PARAM_GAME_PARAM ] == 1 ) && ( pTask->params[ SEARCH_PARAM_GAME_PARAM ] != TMemInvalidHdl ) )
			{
				pSearchParam = ( SCrySessionSearchParam* )( pLobby->MemGetPtr( pTask->params[ SEARCH_PARAM_GAME_PARAM ] ) );
			}
			else
			{
				pSearchParam = NULL;
			}

			if ( ( pTask->numParams[ SEARCH_PARAM_GAME_PARAM_DATA ] > 0 ) && ( pTask->params[ SEARCH_PARAM_GAME_PARAM_DATA ] != TMemInvalidHdl ) )
			{
				pSearchData = ( SCrySessionSearchData* )( pLobby->MemGetPtr( pTask->params[ SEARCH_PARAM_GAME_PARAM_DATA ] ) );
			}
			else
			{
				pSearchData = NULL;
			}

			if ( pSearchParam )
			{
				maxResults = min( pSearchParam->m_maxNumReturn, MAX_GAMESPY_SEARCH_SERVERS );
			}
			else
			{
				maxResults = MAX_GAMESPY_SEARCH_SERVERS;
			}

			SCryGameSpySessionID*	pId = new SCryGameSpySessionID();

			bool	haveNewRecord = ( msg == PEER_ADD ) || ( msg == PEER_UPDATE );
			bool	keepNewRecord = haveNewRecord;
			uint32		newProfile = 0;
			uint32		newTime = 0;

#if GAMESPY_ENABLE_SESSION_PING
			if ( CNetCVars::Get().gamespySessionPing )
			{
				tmpServer.sessionPingState = eSPS_NotStarted;
			}
			else
#endif
			{
				tmpServer.sessionPingState = eSPS_GotPongValidSession;
			}

			tmpServer.firstPingSentTime = CTimeValue();
			tmpServer.lastPingSentTime = CTimeValue();
			tmpServer.result.m_id = pId;
			tmpServer.result.m_data.m_numData = pMatchMaking->m_registeredUserData.num;
			tmpServer.result.m_data.m_data = new SCrySessionUserData[ pMatchMaking->m_registeredUserData.num ];
			tmpServer.result.m_numFilledSlots = 0;
			tmpServer.result.m_numFriends = 0;
			tmpServer.result.m_ping = 0;
			tmpServer.result.m_flags = 0;

			// Apply any filtering that wouldn't fit in the query or the GameSpy master server couldn't do.

			if ( haveNewRecord )
			{
				for ( uint8 i = 0; i < pMatchMaking->m_numServerSearchReportKeys; ++i )
				{
					uint8						key = pMatchMaking->m_serverSearchReportKeys[ i ];
					uint8						keyUserDataIdx = pMatchMaking->m_keyUserDataIdx[ key ];

					switch ( keyUserDataIdx )
					{
					case KEY_USERDATA_IDX_INTERNAL:

						ReadInternalKeyFromSBServer( key, &tmpServer, server );
						break;

					default:

						ReadUserKeyFromSBServer( key, keyUserDataIdx, pMatchMaking->m_registeredUserData.data[ keyUserDataIdx ].m_type, &tmpServer, server );
						break;
					}
				}

				CryLobbyUserDataID	prevId = KEY_USERDATA_IDX_UNUSED;
				CryLobbyUserDataID	thisId;
				bool								passId = true;

				for ( uint8 i = 0; ( i <= pSearchParam->m_numData ) && keepNewRecord; ++i )
				{
					if ( i < pSearchParam->m_numData )
					{
						thisId = pSearchData[ i ].m_data.m_id;
					}
					else
					{
						thisId = KEY_USERDATA_IDX_UNUSED;
					}

					if ( thisId != prevId )
					{
						if ( !passId )
						{
							keepNewRecord = false;
						}

						passId = false;
					}

					if ( ( thisId != KEY_USERDATA_IDX_UNUSED ) && !passId )
					{
						passId = FilterKey( &tmpServer.result.m_data.m_data[ pMatchMaking->m_keyUserDataIdx[ thisId ] ], &pSearchData[ i ] );
					}

					prevId = thisId;
				}

				tmpServer.result.m_ping = SBServerGetPing( server );

				if ( pSearchParam )
				{
					uint32 sessionFreeSlots = tmpServer.result.m_data.m_numPrivateSlots + tmpServer.result.m_data.m_numPublicSlots - tmpServer.result.m_numFilledSlots;

					if ( ( pSearchParam->m_numFreeSlots > sessionFreeSlots ) || ( tmpServer.result.m_data.m_ranked != pSearchParam->m_ranked ) )
					{
						keepNewRecord = false;
					}
				}

				if ( pId->m_sid )
				{
					newProfile = pId->m_sid >> GAMESPY_SESSION_ID_PROFILE_SHIFT;
					newTime = pId->m_sid & GAMESPY_SESSION_ID_TIME_MASK;
				}
				else
				{
					keepNewRecord = false;
				}

				if ( !( tmpServer.createFlags & CRYSESSION_CREATE_FLAG_SEARCHABLE ) )
				{
					keepNewRecord = false;
				}
			}

			// Find record(s) that the new one replaces.

			for ( uint32 i = 0; ( i < pSearch->numServers ) && ( removeOldRecordCount < 2 ); ++i )
			{
				if ( pSearch->servers[ i ].server == server )
				{
					if ((pSearch->servers[ i ].sessionPingState != eSPS_ReportedToGame) || (msg!=PEER_UPDATE))
					{
						removeOldRecords[ removeOldRecordCount++ ] = i;
						continue;
					}
					else
					{
						keepNewRecord=false;
						continue;
					}
				}

				if ( newProfile && newTime )
				{
					uint32		oldProfile = static_cast< SCryGameSpySessionID* >( pSearch->servers[ i ].result.m_id.get() )->m_sid >> GAMESPY_SESSION_ID_PROFILE_SHIFT;
					uint32		oldTime = static_cast< SCryGameSpySessionID* >( pSearch->servers[ i ].result.m_id.get() )->m_sid & GAMESPY_SESSION_ID_TIME_MASK;

					if ( oldProfile == newProfile )
					{
						if ( oldTime < newTime )
						{
							removeOldRecords[ removeOldRecordCount++ ] = i;
						}
						else
						{
							keepNewRecord = false;
						}
					}
				}
			}

			// Replace old record(s) with new record.

			while ( removeOldRecordCount )
			{
				--removeOldRecordCount;

				if ( removeOldRecords[ removeOldRecordCount ] == pSearch->numServers - 1 )
				{
					FreeSessionSearchServer( &pSearch->servers[ removeOldRecords[ removeOldRecordCount ] ] );
				}
				else
				{
					MoveSessionSearchServer( &pSearch->servers[ removeOldRecords[ removeOldRecordCount ] ], &pSearch->servers[ pSearch->numServers - 1 ] );
				}

				--pSearch->numServers;
			}

			if ( keepNewRecord && ( pSearch->numServers < maxResults ) )
			{
				uint32 useIP = 0;
				uint16 usePort = 0;
				uint32 publicIP = SBServerGetPublicInetAddress( server );
				uint16 publicPort = htons( SBServerGetPublicQueryPort( server ) );
				uint32 privateIP = 0;
				uint16 privatePort = 0;
				ICryFriends*	pFriends = pLobbyService->GetFriends();

				if ( pFriends )
				{
					CCryGameSpyFriends*	pGameSpyFriends = static_cast< CCryGameSpyFriends* >( pFriends );
					CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >	url;

					pMatchMaking->CreateSessionURL( publicIP, publicPort, pId->m_sid, url );
					tmpServer.result.m_numFriends = pGameSpyFriends->GetFriendCountInSession( url.c_str() );
				}
				else
				{
					tmpServer.result.m_numFriends = 0;
				}

				if ( SBServerHasPrivateAddress( server ) )
				{
					privateIP = SBServerGetPrivateInetAddress( server );
					privatePort = htons( SBServerGetPrivateQueryPort( server ) );
				}
				else
				{
					if ( SBServerDirectConnect( server ) )
					{
						useIP = publicIP;
						usePort = publicPort;
					}
				}

				pId->m_addr = pMatchMaking->CreateRemoteConnectionID( useIP, usePort, publicIP, publicPort, privateIP, privatePort );
				tmpServer.server = server;
				MoveSessionSearchServer( &pSearch->servers[ pSearch->numServers ], &tmpServer );
				++pSearch->numServers;

				if ( pSearch->numServers == maxResults )
				{
					pSearch->finished = true;
				}
			}

			break;
		}
	}
}


void CCryGameSpyMatchMaking::ReadInternalKeyFromSBServer( uint8 key, SSearch::SServer* pServer, SBServer sbServer )
{
	switch ( key )
	{
	case eCGSK_HostNameKey:
		strncpy( pServer->result.m_data.m_name, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ), MAX_SESSION_NAME_LENGTH );
		pServer->result.m_data.m_name[ MAX_SESSION_NAME_LENGTH - 1 ] = '\0';
		break;
	case eCGSK_NumPlayersKey:
		pServer->result.m_numFilledSlots = SBServerGetIntValue( sbServer, qr2_registered_key_list[ key ], 0 );
		break;
	case eCGSK_GameModeKey:
		{
			const char*	pGameMode = SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" );
			for ( pServer->mode = eGM_OpenWaiting; pServer->mode < eGM_NumGameModes; pServer->mode = ( EGameMode )( pServer->mode + 1 ) )
			{
				if ( !strcmp( pGameMode, k_gameModeNames[ pServer->mode ] ) )
				{
					break;
				}
			}
		}
		break;
	case eCGSK_PasswordRequiredKey:
		if ( SBServerGetBoolValue( sbServer, qr2_registered_key_list[ key ], SBFalse ) == SBTrue )
		{
			pServer->result.m_flags |= eCSSRF_RequirePassword;
		}
		break;
	case eCGSK_SessionIDKey:
		DecodeU64( static_cast< SCryGameSpySessionID* >( pServer->result.m_id.get() )->m_sid, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ) );
		break;
	case eCGSK_NumPrivateSlotsKey:
		DecodeU32( pServer->result.m_data.m_numPrivateSlots, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ) );
		break;
	case eCGSK_NumPublicSlotsKey:
		DecodeU32( pServer->result.m_data.m_numPublicSlots, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ) );
		break;
	case eCGSK_RankedKey:
		pServer->result.m_data.m_ranked = SBServerGetIntValue( sbServer, qr2_registered_key_list[ key ], 0 ) != 0;
		break;
	case eCGSK_CreateFlagsKey:
		DecodeU32( pServer->createFlags, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ) );
		break;
	case eCGSK_NumMigrationsKey:
		DecodeU32( pServer->numMigrations, SBServerGetStringValue( sbServer, qr2_registered_key_list[ key ], "" ) );
		break;
	case eCGSK_DedicatedKey:
		pServer->dedicated = SBServerGetBoolValue( sbServer, qr2_registered_key_list[ key ], SBFalse ) == SBTrue;
		break;
	}
}


void CCryGameSpyMatchMaking::ReadUserKeyFromSBServer( uint8 key, uint8 keyUserDataIdx, ECryLobbyUserDataType type, SSearch::SServer* pServer, SBServer server )
{
	pServer->result.m_data.m_data[ keyUserDataIdx ].m_type = type;
	pServer->result.m_data.m_data[ keyUserDataIdx ].m_id = key;

	switch ( key )
	{
		case eCGSK_RegionKey:
			pServer->result.m_data.m_data[ keyUserDataIdx ].m_int32 = SBServerGetIntValue( server, qr2_registered_key_list[ key ], 0 );
			break;

		default:
			switch ( type )
			{
			case eCLUDT_Int64:
				DecodeS64( pServer->result.m_data.m_data[ keyUserDataIdx ].m_int64, SBServerGetStringValue( server, qr2_registered_key_list[ key ], "" ) );
				break;
			case eCLUDT_Int32:
				DecodeS32( pServer->result.m_data.m_data[ keyUserDataIdx ].m_int32, SBServerGetStringValue( server, qr2_registered_key_list[ key ], "" ) );
				break;
			case eCLUDT_Int16:
				DecodeS16( pServer->result.m_data.m_data[ keyUserDataIdx ].m_int16, SBServerGetStringValue( server, qr2_registered_key_list[ key ], "" ) );
				break;
			case eCLUDT_Int8:
				DecodeS8( pServer->result.m_data.m_data[ keyUserDataIdx ].m_int8, SBServerGetStringValue( server, qr2_registered_key_list[ key ], "" ) );
				break;
			case eCLUDT_Float64:
				pServer->result.m_data.m_data[ keyUserDataIdx ].m_f64 = SBServerGetFloatValue( server, qr2_registered_key_list[ key ], 0.0 );
				break;
			case eCLUDT_Float32:
				pServer->result.m_data.m_data[ keyUserDataIdx ].m_f32 = ( float )SBServerGetFloatValue( server, qr2_registered_key_list[ key ], 0.0 );
				break;
			case eCLUDT_Int64NoEndianSwap:
				DecodeS64( pServer->result.m_data.m_data[ keyUserDataIdx ].m_int64, SBServerGetStringValue( server, qr2_registered_key_list[ key ], "" ) );
				break;
			}
			break;
	}
}


bool CCryGameSpyMatchMaking::FilterKey( SCryLobbyUserData* pSessionData, SCrySessionSearchData* pSearchData )
{
	bool										result = true;

	int64										sessionInt;
	int64										searchInt;
	f64											sessionFloat;
	f64											searchFloat;
	uint32									type;

	switch ( pSearchData->m_data.m_type )
	{
	case eCLUDT_Int64:
		sessionInt = pSessionData->m_int64;
		searchInt = pSearchData->m_data.m_int64;
		type = eCLUDT_Int64;
		break;
	case eCLUDT_Int32:
		sessionInt = pSessionData->m_int32;
		searchInt = pSearchData->m_data.m_int32;
		type = eCLUDT_Int64;
		break;
	case eCLUDT_Int16:
		sessionInt = pSessionData->m_int16;
		searchInt = pSearchData->m_data.m_int16;
		type = eCLUDT_Int64;
		break;
	case eCLUDT_Int8:
		sessionInt = pSessionData->m_int8;
		searchInt = pSearchData->m_data.m_int8;
		type = eCLUDT_Int64;
		break;
	case eCLUDT_Float64:
		sessionFloat = pSessionData->m_f64;
		searchFloat = pSearchData->m_data.m_f64;
		type = eCLUDT_Float64;
		break;
	case eCLUDT_Float32:
		sessionFloat = pSessionData->m_f32;
		searchFloat = pSearchData->m_data.m_f32;
		type = eCLUDT_Float64;
		break;
	case eCLUDT_Int64NoEndianSwap:
		sessionInt = SwapEndianValue( pSessionData->m_int64, eBigEndian );
		searchInt = SwapEndianValue( pSearchData->m_data.m_int64, eBigEndian );
		type = eCLUDT_Int64;
		break;
	}

	switch ( type | ( pSearchData->m_operator << 16 ) )
	{
	case eCLUDTO_Int64Equal:
		result = ( sessionInt == searchInt );
		break;
	case eCLUDTO_Int64NotEqual:
		result = ( sessionInt != searchInt );
		break;
	case eCLUDTO_Int64LessThan:
		result = ( sessionInt < searchInt );
		break;
	case eCLUDTO_Int64LessThanEqual:
		result = ( sessionInt <= searchInt );
		break;
	case eCLUDTO_Int64GreaterThan:
		result = ( sessionInt > searchInt );
		break;
	case eCLUDTO_Int64GreaterThanEqual:
		result = ( sessionInt >= searchInt );
		break;
	case eCLUDTO_Int64BitwiseAndNotEqualZero:
		result = ( sessionInt & searchInt ) != 0;
		break;
	case eCLUDTO_Float64Equal:
		result = ( sessionFloat == searchFloat ); // Do other online services really do this without an epsilon?
		break;
	case eCLUDTO_Float64NotEqual:
		result = ( sessionFloat != searchFloat ); // Do other online services really do this without an epsilon?
		break;
	case eCLUDTO_Float64LessThan:
		result = ( sessionFloat < searchFloat );
		break;
	case eCLUDTO_Float64LessThanEqual:
		result = ( sessionFloat <= searchFloat );
		break;
	case eCLUDTO_Float64GreaterThan:
		result = ( sessionFloat > searchFloat );
		break;
	case eCLUDTO_Float64GreaterThanEqual:
		result = ( sessionFloat >= searchFloat );
		break;
	}

	return result;
}


// Description:
//	 Session user data event.
// Arguments:
//	 event - event
//	 localSessionIndex - session handle
//	 id - connection ID
void CCryGameSpyMatchMaking::SessionUserDataEvent( ECryLobbySystemEvent event, CrySessionHandle localSessionIndex, CryMatchMakingConnectionID id )
{
	SSession*				pSession = &m_sessions[ localSessionIndex ];

	if ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_USER_DATA_EVENTS_STARTED )
	{
		SCryUserInfoResult		userInfo;
		SCryGameSpyUserID*		pUserID = new SCryGameSpyUserID();

		if ( pUserID )
		{
			if ( id == CryMatchMakingInvalidConnectionID )
			{
				SSession::SLConnection* pConnection = &pSession->localConnection;
				userInfo.m_conID = pConnection->uid;
				userInfo.m_isDedicated = false;				// Dedicated server never uses this result
				strncpy( userInfo.m_userName, pConnection->name, CRYLOBBY_USER_NAME_LENGTH );
				userInfo.m_userName[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = 0;
				memcpy( userInfo.m_userData, pConnection->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
				pUserID->m_profileID = pConnection->userID.m_profileID;
			}
			else
			{
				SSession::SRConnection* pConnection = &pSession->remoteConnection[ id ];

				userInfo.m_conID = pConnection->uid;
				userInfo.m_isDedicated = pConnection->m_isDedicated;
				strncpy( userInfo.m_userName, pConnection->name, CRYLOBBY_USER_NAME_LENGTH );
				userInfo.m_userName[ CRYLOBBY_USER_NAME_LENGTH - 1 ] = 0;
				memcpy( userInfo.m_userData, pConnection->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
				pUserID->m_profileID = pConnection->m_profileID;
			}

			userInfo.m_userID = pUserID;
			CCryMatchMaking::SessionUserDataEvent( event, localSessionIndex, &userInfo );
		}
	}
}


// Description:
//	 Initial session user data events.
// Arguments:
//	 localSessionIndex - session handle
void CCryGameSpyMatchMaking::InitialUserDataEvent( CrySessionHandle localSessionIndex )
{
	SSession*				pSession = &m_sessions[ localSessionIndex ];

	pSession->localFlags |= CRYSESSION_LOCAL_FLAG_USER_DATA_EVENTS_STARTED;

	if ( pSession->localConnection.used )
	{
		SessionUserDataEvent( eCLSE_SessionUserJoin, localSessionIndex, CryMatchMakingInvalidConnectionID );
	}

	for (uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; i++)
	{
		if ( pSession->remoteConnection[ i ].used )
		{
			SessionUserDataEvent( eCLSE_SessionUserJoin, localSessionIndex, i );
		}
	}
}

void CCryGameSpyMatchMaking::StartSessionStart( CryMatchMakingTaskID mmTaskID )
{
	STask* pTask = &m_task[ mmTaskID ];
	SSession* pSession = &m_sessions[ pTask->session ];

	if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) )
	{
		InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer );
	}
}

void CCryGameSpyMatchMaking::TickSessionStart( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSession*				pSession = &m_sessions[ pTask->session ];

	if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) )
	{
		GetService()->PeerStartGame();
	}

	StopTaskRunning( mmTaskID );
}

void CCryGameSpyMatchMaking::StartSessionEnd( CryMatchMakingTaskID mmTaskID )
{
	STask* pTask = &m_task[ mmTaskID ];
	SSession* pSession = &m_sessions[ pTask->session ];

	if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) )
	{
		InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer );
	}
}

void CCryGameSpyMatchMaking::TickSessionEnd( CryMatchMakingTaskID mmTaskID )
{
	STask*					pTask = &m_task[ mmTaskID ];
	SSession*				pSession = &m_sessions[ pTask->session ];

	if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && (pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) )
	{
		GetService()->PeerStopGame();
	}

	StopTaskRunning( mmTaskID );
}


// Description:
//	 Are we the host for the given session?
// Arguments:
//	 handle - session handle
// Return:
//	 true if we are the host.
bool CCryGameSpyMatchMaking::IsHost( CrySessionHandle handle )
{
	return (m_sessions[ handle ].localFlags & CRYSESSION_LOCAL_FLAG_HOST) == CRYSESSION_LOCAL_FLAG_HOST;
}


// Description:
//	 Get a session ID from a session handle.
// Arguments:
//	 h - session handle
// Return:
//	 Session ID.
uint64 CCryGameSpyMatchMaking::GetSIDFromSessionHandle( CrySessionHandle handle )
{
	CRY_ASSERT_MESSAGE( ( handle < MAX_MATCHMAKING_SESSIONS ) && ( m_sessions[ handle ].localFlags & CRYSESSION_LOCAL_FLAG_USED ), "CCryGameSpyMatchMaking::GetSIDFromSessionHandle: invalid session handle" );
	
	return m_sessions[ handle ].id.m_sid;
}


CCryGameSpyLobbyService* CCryGameSpyMatchMaking::GetService()
{
	return static_cast< CCryGameSpyLobbyService* >( m_pService );
}


// Description:
//	 Process a join request packet.
// Arguments:
//	 addr - sender
//	 pPacket - packet
void CCryGameSpyMatchMaking::ProcessSessionRequestJoin( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	ECryLobbyError				error = eCLE_Success;
	uint32								bufferPos = 0;
	uint64								sessionID;
	CrySessionHandle			localSessionIndex;
	CryLobbyConnectionID	c;
	CryMatchMakingTaskID	returnTaskID;

	CCryLobby* pLobby = static_cast<CCryLobby*>(gEnv->pNetwork->GetLobby());
	uint8 maxLen = 32;

	if (pLobby != NULL)
	{
		SConfigurationParams neededInfo[1] = { CLCC_MATCHMAKING_SESSION_PASSWORD_MAX_LENGTH, {NULL} };
		pLobby->GetConfigurationInformation(neededInfo, 1);
		maxLen = neededInfo[0].m_8;
	}

	TMemHdl h = m_lobby->MemAlloc(maxLen);
	char* pBuffer = static_cast<char*>(m_lobby->MemGetPtr(h));
	CRY_ASSERT(pBuffer != NULL);
	if (pBuffer != NULL)
	{
		memset(pBuffer, 0, maxLen);

		pPacket->StartRead();
		returnTaskID = pPacket->ReadUINT8();
		sessionID = pPacket->GetFromSessionSID();
		pPacket->ReadData(pBuffer, maxLen);

		bool passwordOK = true;
		if (gEnv->pConsole != NULL)
		{
			ICVar* pPass = gEnv->pConsole->GetCVar("sv_password");
			const char* pString = pPass->GetString();
			if (strlen(pString) > 0)
			{
				if (strncmp(pBuffer, pString, maxLen))
				{
					passwordOK = false;
				}
			}
		}

		m_lobby->MemFree(h);

		if (passwordOK)
		{
			if ( m_lobby->ConnectionFromAddress( &c, addr ) )
			{
				sockaddr_in						saddr;

				if ( ConvertAddr( addr, &saddr ) )
				{
					uint32								publicIP = pPacket->ReadUINT32();
					uint16								publicPort = pPacket->ReadUINT16();
					uint32								privateIP = pPacket->ReadUINT32();
					uint16								privatePort = pPacket->ReadUINT16();

					// If the 'connect' command is used from the console with no session then we'll have an invalid
					// session ID here.  In this case, search for the first hosted searchable nub session and use that.
					if ( sessionID == GAMESPY_INVALID_SESSION_ID )
					{
						for ( localSessionIndex = 0; localSessionIndex < MAX_MATCHMAKING_SESSIONS; ++localSessionIndex )
						{
							SSession*				pSession = &m_sessions[ localSessionIndex ];

							if ( ( ( pSession->localFlags & ( CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST ) ) == ( CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST ) )  &&
									 ( ( pSession->createFlags & ( CRYSESSION_CREATE_FLAG_SEARCHABLE | CRYSESSION_CREATE_FLAG_NUB ) ) == ( CRYSESSION_CREATE_FLAG_SEARCHABLE | CRYSESSION_CREATE_FLAG_NUB ) ) )
							{
								break;
							}				
						}

						if ( localSessionIndex == MAX_MATCHMAKING_SESSIONS )
						{
							localSessionIndex = CrySessionInvalidHandle;
						}
					}
					else
					{
						localSessionIndex = FindSessionFromSessionID( sessionID );
					}

					if ( ( localSessionIndex != CrySessionInvalidHandle ) && ((m_sessions[ localSessionIndex ].localFlags & (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) == (CRYSESSION_LOCAL_FLAG_USED | CRYSESSION_LOCAL_FLAG_HOST)) )
					{
						SSession*					pSession = &m_sessions[ localSessionIndex ];
						bool							crossover = false;

						if ( !( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) )
						{
							for ( CryMatchMakingTaskID joinTaskID = 0; ( joinTaskID < MAX_MATCHMAKING_TASKS ) && !crossover; ++joinTaskID )
							{
								STask*						pTask = &m_task[ joinTaskID ];

								if ( pTask->used && ( pTask->startedTask == eT_SessionJoin ) && !( m_sessions[ pTask->session ].createFlags & CRYSESSION_CREATE_FLAG_NUB ) )
								{
									crossover = true;
								}
							}
						}

						if ( !crossover )
						{
#if NETWORK_HOST_MIGRATION
							if ((pSession->hostMigrationInfo.m_state == eHMS_Idle) || (pSession->hostMigrationInfo.m_state >= eHMS_PromoteToServer))
#endif
							{
								uint8							numUsers = pPacket->ReadUINT8();

								if ( pSession->numFilledSlots + numUsers <= pSession->data.m_numPublicSlots + pSession->data.m_numPrivateSlots )
								{
									char					name[ CRYLOBBY_USER_NAME_LENGTH ];
									uint8					userData[ CRYLOBBY_USER_DATA_SIZE_IN_BYTES ];
									GPProfile			profileID;
									CryMatchMakingConnectionID id = CryMatchMakingInvalidConnectionID;

									pPacket->ReadString( name, CRYLOBBY_USER_NAME_LENGTH );
									pPacket->ReadData( userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES );
									profileID = pPacket->ReadUINT32();

									const SCryGameSpyUserID* pUserID = NULL;
									for (uint32 bannedIndex = 0; bannedIndex < m_bannedUser.size(); ++bannedIndex)
									{
										pUserID = static_cast<const SCryGameSpyUserID*>(m_bannedUser[bannedIndex].m_userID.get());
										if (pUserID->m_profileID == profileID)
										{
											error = eCLE_Banned;
											break;
										}
									}

									if (error == eCLE_Success)
									{
										for ( uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; ++i )
										{
											if ( pSession->remoteConnection[ i ].used && (pSession->remoteConnection[ i ].m_profileID == profileID ) )
											{
												id = i;
											}
										}

										if ( id == CryMatchMakingInvalidConnectionID )
										{
											id = AddRemoteConnection( localSessionIndex, c, CreateConnectionUID( localSessionIndex ), saddr.sin_addr.s_addr, saddr.sin_port, publicIP, publicPort, privateIP, privatePort, numUsers, name, userData, false );

											if ( id != CryMatchMakingInvalidConnectionID )
											{
												pSession->remoteConnection[ id ].m_profileID = profileID;

#if USE_CRYLOBBY_GAMESPY_VOIP
												if (!(pSession->remoteConnection[id].m_isDedicated))
												{
													InformVoiceRemoteUsersJoined(localSessionIndex, id);
												}
#endif
											}
										}

										if ( id != CryMatchMakingInvalidConnectionID )
										{									
											SSession::SRConnection*	pConnection = &pSession->remoteConnection[ id ];
#ifndef _RELEASE
											if ( CNetCVars::Get().gamespyCheckCDKey )
#endif
											{
												if ( pConnection->m_cdKeyChallengeState == eCDKCS_NotStarted )
												{
													pConnection->m_joinTaskID = returnTaskID;
													SendCDKeyChallenge( pConnection, NULL );
												}
											}
#ifndef _RELEASE
											else
											{
												error = SessionJoinSuccess( pSession, id, returnTaskID, localSessionIndex );
											}
#endif
										}
										else
										{
											error = eCLE_ConnectionFailed;
										}
									}
								}
								else
								{
									error = eCLE_SessionFull;
								}
							}
#if NETWORK_HOST_MIGRATION
							else
							{
								error = eCLE_SessionWrongState;
							}
#endif
						}
						else
						{
							error = eCLE_ConnectionFailed;
						}			
					}
					else
					{
						error = eCLE_ConnectionFailed;
					}			
				}
				else
				{
					error = eCLE_ConnectionFailed;
				}			
			}
			else
			{
				error = eCLE_ConnectionFailed;
			}
		}
		else
		{
			error = eCLE_PasswordIncorrect;
		}
	}
	else
	{
		error = eCLE_OutOfMemory;
	}

	if (error != eCLE_Success)
	{
		if(error == eCLE_ConnectionFailed)
		{
			gEnv->pGame->AddGameWarning("GSUnavailable", NULL);
		}
			
		SessionJoinFail( sessionID, error, returnTaskID, addr );
	}

	NetLog("[Lobby] Processed Session request join packet error %d", error);
}


// Description:
//	 Process an add remote connections packet.
// Arguments:
//	 addr - sender
//	 pPacket - packet
void CCryGameSpyMatchMaking::ProcessSessionAddRemoteConnections( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	uint32						bufferPos = 0;
	uint64						sessionID;
	CrySessionHandle	localSessionIndex;

	pPacket->StartRead();
	sessionID = pPacket->GetFromSessionSID();

	CryLog("[Lobby] Got new connections for session %llX", sessionID);

	localSessionIndex = FindSessionFromSessionID( sessionID );

	if ( localSessionIndex != CrySessionInvalidHandle )
	{
		SSession*				pSession = &m_sessions[ localSessionIndex ];
		if ((pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST) == 0)
		{
			uint8						numConnections = pPacket->ReadUINT8();

			for (uint32 i = 0; i < numConnections; i++)
			{
				char name[CRYLOBBY_USER_NAME_LENGTH];
				uint8 userData[CRYLOBBY_USER_DATA_SIZE_IN_BYTES];
				GPProfile profileID;
				uint32	publicIP;
				uint16	publicPort;
				uint32	privateIP;
				uint16	privatePort;
				uint8 numUsers;
				bool isDedicated;
				SCryMatchMakingConnectionUID connectionUID;
				uint8 NATClientIndex;
				uint32 NATCookie;

				publicIP = pPacket->ReadUINT32();
				publicPort = pPacket->ReadUINT16();
				privateIP = pPacket->ReadUINT32();
				privatePort = pPacket->ReadUINT16();
				connectionUID = pPacket->ReadConnectionUID();
				numUsers = pPacket->ReadUINT8();
				isDedicated = pPacket->ReadBool();
				pPacket->ReadString(name,CRYLOBBY_USER_NAME_LENGTH);
				pPacket->ReadData(userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);
				profileID = pPacket->ReadUINT32();

				NATClientIndex = pPacket->ReadUINT8();
				NATCookie = pPacket->ReadUINT32();

				CryMatchMakingConnectionID id = AddRemoteConnection( localSessionIndex, CryLobbyInvalidConnectionID, connectionUID, 0, 0, publicIP, publicPort, privateIP, privatePort, numUsers, name, userData, isDedicated );

				if (id != CryMatchMakingInvalidConnectionID)
				{
					SSession* pSession = &m_sessions[ localSessionIndex ];
					SSession::SRConnection* pConnection = &pSession->remoteConnection[id];

					CryLog("[Lobby] Add new connection %d uid %d cookie %08x", id, connectionUID.m_uid, NATCookie);

					pConnection->m_profileID = profileID;

#if USE_CRYLOBBY_GAMESPY_VOIP
					if (!(pSession->remoteConnection[id].m_isDedicated))
					{
						InformVoiceRemoteUsersJoined(localSessionIndex, id);
					}
#endif

					StartFindUseAddress(&pConnection->id, NATCookie, NATClientIndex, false);
					SessionUserDataEvent(eCLSE_SessionUserJoin, localSessionIndex, id);

					if (pConnection->id.m_state == eGSNT_Succeeded)
					{
						// Already succeeded so there will be no NATNegotiateCompletedCallback callback set connectionID now.
						pConnection->connectionID = GetLobbyConnectionFromRemoteConnectionID(pConnection->id);
						m_lobby->ConnectionAddRef(pConnection->connectionID);

#if USE_CRYLOBBY_GAMESPY_VOIP
						CCryGameSpyVoice*				pVoice = static_cast< CCryGameSpyVoice* >( m_pService->GetVoice() );
						if ( pVoice )
						{
							SCryGameSpyUserID*	pUserID = new	SCryGameSpyUserID( pConnection->m_profileID );

							if ( pUserID && !(pConnection->m_isDedicated))
							{
								pVoice->LateSetUserLobbyConnection( pConnection->connectionID, pUserID );
							}
						}
#endif
					}
				}
			}
		}
		else
		{
			NetLog("[Lobby] CCryLANMatchMaking::ProcessSessionAddRemoteConnections() packet received on session %u but we're the host!  Ignoring", localSessionIndex);
		}
	}
}


// Description:
//	 Process a join request result packet.
// Arguments:
//	 addr - sender
//	 pPacket - packet
void CCryGameSpyMatchMaking::ProcessSessionRequestJoinResult( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	ECryLobbyError				error = eCLE_Success;
	CryLobbyConnectionID	c;

	if ( m_lobby->ConnectionFromAddress( &c, addr ) )
	{
		uint64 sessionID;
		CryMatchMakingTaskID	mmTaskID;
		uint32	publicIP;
		uint16	publicPort;

		pPacket->StartRead();
		error = pPacket->ReadError();
		sessionID = pPacket->GetFromSessionSID();
		mmTaskID = pPacket->ReadUINT8();
		publicIP = pPacket->ReadUINT32();
		publicPort = pPacket->ReadUINT16();

		mmTaskID = FindTaskFromTaskTaskID( eT_SessionJoin, mmTaskID );

		if ( mmTaskID != CryMatchMakingInvalidTaskID )
		{
			STask* pTask = &m_task[mmTaskID];
			SSession* pSession = &m_sessions[pTask->session];

			// If this is a join from console, populate data that we might not have had
			// at the time.

			if ( pSession->id.m_sid == GAMESPY_INVALID_SESSION_ID )
			{
				pSession->id.m_sid = sessionID;
			}

			if ( ( pSession->id.m_addr.m_publicIP == 0 ) && ( pSession->id.m_addr.m_publicPort == 0 ) )
			{
				pSession->id.m_addr.m_publicIP = publicIP;
				pSession->id.m_addr.m_publicPort = publicPort;
			}

			if ( !pTask->canceled && (pSession->id.m_sid == sessionID) )
			{
				sockaddr_in saddr;

				if (ConvertAddr(addr, &saddr))
				{
					NetLog("[Lobby] Received SessionRequestJoinResult error %d", error);

					UpdateTaskError(mmTaskID, error);

					if (pTask->error == eCLE_Success)
					{
						uint8 numUsers;
						bool isDedicated;
						SCryMatchMakingConnectionUID hostConnectionUID;
						char name[CRYLOBBY_USER_NAME_LENGTH];
						uint8 userData[CRYLOBBY_USER_DATA_SIZE_IN_BYTES];

						pSession->localConnection.uid = pPacket->ReadConnectionUID();
						uint32 sessionCreateGameFlags = pPacket->ReadUINT16();
						pSession->createFlags = (pSession->createFlags & CRYSESSION_CREATE_FLAG_SYSTEM_MASK) | (sessionCreateGameFlags << CRYSESSION_CREATE_FLAG_GAME_FLAGS_SHIFT);

						bool hostMigrationSupported = m_lobby->GetLobbyServiceFlag(m_serviceType, eCLSF_SupportHostMigration);
						if ((pSession->createFlags & CRYSESSION_CREATE_FLAG_MIGRATABLE) && hostMigrationSupported)
						{
							pSession->localFlags |= CRYSESSION_LOCAL_FLAG_CAN_SEND_HOST_HINTS;
						}

						NetLog("[Lobby] Created local connection uid %d in session %llX", pSession->localConnection.uid.m_uid, pSession->id.m_sid);
						if ((!(pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST)) && (pSession->localConnection.uid.m_uid > m_connectionUIDCounter))
						{
							// Keep all remote clients in sync with the host (in case they're chosen to be host during host migration)
							m_connectionUIDCounter = pSession->localConnection.uid.m_uid;
						}

						numUsers = pPacket->ReadUINT8();
						isDedicated = pPacket->ReadBool();
						hostConnectionUID = pPacket->ReadConnectionUID();
						pPacket->ReadString(name,CRYLOBBY_USER_NAME_LENGTH);
						pPacket->ReadData(userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);

						// Add the host as a remote connection.

						CryMatchMakingConnectionID id = AddRemoteConnection( pTask->session, c, hostConnectionUID, pSession->id.m_addr.m_useIP, pSession->id.m_addr.m_usePort, pSession->id.m_addr.m_publicIP, pSession->id.m_addr.m_publicPort, pSession->id.m_addr.m_privateIP, pSession->id.m_addr.m_privatePort, numUsers, name, userData, isDedicated );

						if (id != CryMatchMakingInvalidConnectionID)
						{
							SSessionJoinParam*	pParam = static_cast< SSessionJoinParam* >( m_lobby->MemGetPtr( pTask->params[ JOIN_PARAM ] ) );
							pSession->remoteConnection[ id ].m_profileID = pPacket->ReadUINT32();
							NetLog("[Lobby] Created server connection %d uid %d (profile id %i)", id, hostConnectionUID.m_uid, pSession->remoteConnection[id].m_profileID);
							pSession->hostConnectionID = id;

#if USE_CRYLOBBY_GAMESPY_VOIP
							if (!(pSession->remoteConnection[id].m_isDedicated))
							{
								InformVoiceRemoteUsersJoined(pTask->session, id);
							}
#endif

							pParam->finishedCryNetworkJoin = true;
						}
						else
						{
							UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
						}
					}
				}
				else
				{
					UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
				}
			}

			NetLog("[Lobby] Processed session request join result error %d", pTask->error);
		}
	}
}

CryMatchMakingConnectionID CCryGameSpyMatchMaking::AddRemoteConnection( CrySessionHandle h, CryLobbyConnectionID connectionID, SCryMatchMakingConnectionUID uid, uint32 useIP, uint16 usePort, uint32 publicIP, uint16 publicPort, uint32 privateIP, uint16 privatePort, uint32 numUsers, const char* pName, uint8 userData[ CRYLOBBY_USER_DATA_SIZE_IN_BYTES ], bool isDedicated )
{
	SCryGameSpyRemoteConnectionID	remoteConnectionID = CreateRemoteConnectionID( useIP, usePort, publicIP, publicPort, privateIP, privatePort );

	if ( connectionID == CryLobbyInvalidConnectionID )
	{
		connectionID = GetLobbyConnectionFromRemoteConnectionID( remoteConnectionID );
	}

	CryMatchMakingConnectionID id = CCryMatchMaking::AddRemoteConnection(h, connectionID, uid, numUsers);

	if ( id != CryMatchMakingInvalidConnectionID )
	{
		SSession*				pSession = &m_sessions[ h ];
		SSession::SRConnection* pConnection = &pSession->remoteConnection[ id ];

		pConnection->id = remoteConnectionID;
		pConnection->m_isDedicated = isDedicated;

#if NETWORK_HOST_MIGRATION

		pConnection->m_migrated = (pSession->hostMigrationInfo.m_state != eHMS_Idle) ? true : false;

#endif
		strncpy(pConnection->name,pName,CRYLOBBY_USER_NAME_LENGTH);
		pConnection->name[CRYLOBBY_USER_NAME_LENGTH-1]=0;
		memcpy(pConnection->userData, userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);
		pSession->numFilledSlots += numUsers;
		pConnection->m_cdKeyChallengeState = eCDKCS_NotStarted;
		pConnection->m_cdKeyLocalID = ( h << GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_SHIFT ) | ( id << GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_SHIFT ) | m_cdKeyLocalIDCycle;
		m_cdKeyLocalIDCycle = ( m_cdKeyLocalIDCycle + 1 ) & GAMESPY_CD_KEY_LOCALID_CYCLE_MASK;
	}

	return id;
}

SCryGameSpyRemoteConnectionID CCryGameSpyMatchMaking::CreateRemoteConnectionID( uint32 useIP, uint16 usePort, uint32 publicIP, uint16 publicPort, uint32 privateIP, uint16 privatePort )
{
	SCryGameSpyRemoteConnectionID	result;

	result.m_state = ((useIP == 0) && (usePort == 0)) ? eGSNT_NotStarted : eGSNT_Succeeded;
	result.m_cookie = 0;
	result.m_useIP = useIP;
	result.m_usePort = usePort;
	result.m_publicIP = publicIP;
	result.m_publicPort = publicPort;
	result.m_privateIP = privateIP;
	result.m_privatePort = privatePort;

	return result;
}


CryLobbyConnectionID CCryGameSpyMatchMaking::GetLobbyConnectionFromRemoteConnectionID( SCryGameSpyRemoteConnectionID&	remoteConnectionID  )
{
	CryLobbyConnectionID result = CryLobbyInvalidConnectionID;

	if (remoteConnectionID.m_state == eGSNT_Succeeded)
	{
		TNetAddress netAddr;

		netAddr = TNetAddress( SIPv4Addr( ntohl( remoteConnectionID.m_useIP ), ntohs( remoteConnectionID.m_usePort ) ) );

		result = m_lobby->FindConnection( netAddr );

		if ( result == CryLobbyInvalidConnectionID )
		{
			result = m_lobby->CreateConnection( netAddr );
		}
	}

	return result;
}


CrySessionHandle CCryGameSpyMatchMaking::FindSessionFromSessionID( uint64 sessionID )
{
	for ( uint32 i = 0; i < MAX_MATCHMAKING_SESSIONS; ++i )
	{
		if ( m_sessions[ i ].localFlags & CRYSESSION_LOCAL_FLAG_USED )
		{
			if ( m_sessions[ i ].id.m_sid  == sessionID )
			{
				return i;
			}
		}
	}

	return CrySessionInvalidHandle;
}


void CCryGameSpyMatchMaking::FreeRemoteConnection( CrySessionHandle localSessionIndex, CryMatchMakingConnectionID id )
{
	if (id != CryMatchMakingInvalidConnectionID)
	{
		SSession* pSession = &m_sessions[localSessionIndex];
		SSession::SRConnection* pConnection = &pSession->remoteConnection[id];

		if (pConnection->used)
		{
			SessionUserDataEvent(eCLSE_SessionUserLeave, localSessionIndex, id);
			pSession->numFilledSlots -= pConnection->numUsers;
	#if USE_CRYLOBBY_GAMESPY_VOIP
			InformVoiceRemoteUsersLeft(localSessionIndex, id);
	#endif

	#ifndef _RELEASE
			if ( CNetCVars::Get().gamespyCheckCDKey )
	#endif
			{
				switch ( pConnection->m_cdKeyChallengeState )
				{
				case eCDKCS_AuthValidationRequested:
				case eCDKCS_ReauthChallengeSent:
				case eCDKCS_ReauthValidationRequested:
				case eCDKCS_ValidationSucceeded:
				case eCDKCS_ValidationFailed:
					gcd_disconnect_user( GetService()->GetGameID(), pConnection->m_cdKeyLocalID );
					break;
				case eCDKCS_NotStarted:
				case eCDKCS_AuthChallengeSent:
					break;
				}
			}

			CCryMatchMaking::FreeRemoteConnection(localSessionIndex, id);
		}
	}
}


uint32 CCryGameSpyMatchMaking::GetPublicIP()
{
	return GetService()->GetPublicIP();
}


uint16 CCryGameSpyMatchMaking::GetPublicPort()
{
	return GetService()->GetPublicPort();
}


uint32 CCryGameSpyMatchMaking::GetPrivateIP()
{
	return GetService()->GetPrivateIP();
}


uint16 CCryGameSpyMatchMaking::GetPrivatePort()
{
	return GetService()->GetPrivatePort();
}


const char* CCryGameSpyMatchMaking::SSession::GetLocalUserName(uint32 localUserIndex) const
{
	if (localFlags & CRYSESSION_LOCAL_FLAG_USED)
	{
		return localConnection.name;
	}

	return NULL;
}

const char* CCryGameSpyMatchMaking::GetConnectionName(CCryMatchMaking::SSession::SRConnection* pConnection, uint32 localUserIndex) const
{
	SSession::SRConnection* pPlatformConnection = reinterpret_cast<SSession::SRConnection*>(pConnection);

	if (pPlatformConnection->used)
	{
		return pPlatformConnection->name;
	}

	return NULL;
}

uint64 CCryGameSpyMatchMaking::GetConnectionUserID(CCryMatchMaking::SSession::SRConnection* pConnection, uint32 localUserIndex) const
{
	SSession::SRConnection* pPlatformConnection = reinterpret_cast<SSession::SRConnection*>(pConnection);

	if (pPlatformConnection->used)
	{
		return pPlatformConnection->m_profileID;
	}

	return INVALID_USER_ID;
}

void CCryGameSpyMatchMaking::BanCmd(uint64 userID, float timeout)
{
	LOBBY_AUTO_LOCK;	

	const SCryGameSpyUserID* pBannedUser = NULL;
	float seconds = timeout * 60.0f;
	bool banned = false;

	for (uint32 index = 0; (!banned) && (index < m_bannedUser.size()); ++index)
	{
		pBannedUser = static_cast<const SCryGameSpyUserID*>(m_bannedUser[index].m_userID.get());
		if (pBannedUser->m_profileID == static_cast<GPProfile>(userID))
		{
			// Just alter the time for the existing banned user
			m_bannedUser[index].m_bannedUntil.SetSeconds(g_time.GetSeconds() + static_cast<float>(seconds));
			banned = true;
		}
	}

	if (!banned)
	{
		// Add a new ban
		SBannedUser bannedUser;
		SCryGameSpyUserID* pNewBannedUser = new SCryGameSpyUserID();
		pNewBannedUser->m_profileID = static_cast<GPProfile>(userID);
		bannedUser.m_userID = pNewBannedUser;
		bannedUser.m_bannedUntil.SetSeconds(g_time.GetSeconds() + static_cast<float>(seconds));

		m_bannedUser.push_back(bannedUser);

		// Kick 'em
		KickCmd(CryLobbyInvalidConnectionID, userID, NULL, eDC_Kicked);
	}
}

void CCryGameSpyMatchMaking::UnbanCmd(uint64 userID)
{
	LOBBY_AUTO_LOCK;	

	const SCryGameSpyUserID* pBannedUser = NULL;

	for (uint32 index = 0; index < m_bannedUser.size(); ++index)
	{
		pBannedUser = static_cast<const SCryGameSpyUserID*>(m_bannedUser[index].m_userID.get());
		if (pBannedUser->m_profileID == static_cast<GPProfile>(userID))
		{
			if (index < (m_bannedUser.size() - 1))
			{
				m_bannedUser[index] = m_bannedUser[m_bannedUser.size() - 1];
			}
			m_bannedUser.resize(m_bannedUser.size() - 1);
			break;
		}
	}
}

void CCryGameSpyMatchMaking::BanStatus()
{
	LOBBY_AUTO_LOCK;	

	CryLogAlways("-----------------------------------------");
	CryLogAlways("Banned players : ");

	const SCryGameSpyUserID* pBannedUser = NULL;

	for (uint32 index = 0; index < m_bannedUser.size(); ++index)
	{
		pBannedUser = static_cast<const SCryGameSpyUserID*>(m_bannedUser[index].m_userID.get());
		uint32 secondsRemaining = static_cast<uint32>(m_bannedUser[index].m_bannedUntil.GetDifferenceInSeconds(g_time));
		uint32 minutesRemaing = secondsRemaining / 60;
		secondsRemaining %= 60;

		CryLogAlways("profile : %d, time left : %d:%02d", pBannedUser->m_profileID, minutesRemaing, secondsRemaining);
	}

	CryLogAlways("-----------------------------------------");
}

void CCryGameSpyMatchMaking::Ban(const CryUserID* pUserID, float timeout)
{
	if (pUserID != NULL)
	{
		if (timeout == USE_DEFAULT_BAN_TIMEOUT)
		{
			if (ICVar* pBanTimeout = gEnv->pConsole->GetCVar("ban_timeout"))
			{
				timeout = pBanTimeout->GetFVal();
			}
		}

		const SCryGameSpyUserID* pGameSpyUserID = static_cast<const SCryGameSpyUserID*>(pUserID->get());
		BanCmd(pGameSpyUserID->m_profileID, timeout);
	}
}

#if USE_CRYLOBBY_GAMESPY_VOIP
void CCryGameSpyMatchMaking::InformVoiceRemoteUsersJoined(CrySessionHandle h, CryMatchMakingConnectionID id)
{
	CCryGameSpyVoice* pVoice = (CCryGameSpyVoice*)m_pService->GetVoice();

	if (pVoice != NULL)
	{
		SSession* pSession = &m_sessions[h];
		SSession::SRConnection* pConnection = &pSession->remoteConnection[id];
		
		SCryGameSpyUserID *pGameSpyID = new SCryGameSpyUserID();
		CryUserID userID = pGameSpyID;
		
		pGameSpyID->m_profileID = pConnection->m_profileID;
		
		pVoice->RegisterRemoteUser(pConnection->connectionID, userID);
	}
}

void CCryGameSpyMatchMaking::InformVoiceRemoteUsersLeft(CrySessionHandle h, CryMatchMakingConnectionID id)
{
	CCryGameSpyVoice* pVoice = (CCryGameSpyVoice*)m_pService->GetVoice();

	if (pVoice != NULL)
	{
		SSession* pSession = &m_sessions[h];
		SSession::SRConnection* pConnection = &pSession->remoteConnection[id];
		pVoice->UnRegisterRemoteUser(pConnection->connectionID);
	}
}
#endif // USE_CRYLOBBY_GAMESPY_VOIP


void CCryGameSpyMatchMaking::ListKeys( qr2_key_type type, qr2_keybuffer_t keyBuffer )
{
	switch ( type )
	{
	case key_server:

		for ( int i = 0; i < m_numServerReportKeys; ++i )
		{
			qr2_keybuffer_add( keyBuffer, m_serverReportKeys[ i ] );
		}

		break;

	case key_player:

		for ( int i = 0; i < m_numPlayerKeys; ++i )
		{
			qr2_keybuffer_add( keyBuffer, m_playerKeys[ i ] );
		}

		break;

	case key_team:

		for ( int i = 0; i < m_numTeamKeys; ++i )
		{
			qr2_keybuffer_add( keyBuffer, m_teamKeys[ i ] );
		}

		break;
	}
}


void CCryGameSpyMatchMaking::ClearTaskLinks()
{
	m_taskLinks.clear();
}


uint32 CCryGameSpyMatchMaking::GetCurrentSessionPlayerCount()
{
	uint32								result = 0;
	CrySessionHandle			localSessionIndex = GetCurrentHostedNetNubSessionHandle();

	if ( localSessionIndex != CrySessionInvalidHandle )
	{
		result = m_sessions[ localSessionIndex ].numFilledSlots;
	}

	return result;
}


CrySessionHandle CCryGameSpyMatchMaking::GetCurrentInvitableSessionHandle() const
{
	CrySessionHandle handle = CrySessionInvalidHandle;

	for ( uint32 i = 0; i < MAX_MATCHMAKING_SESSIONS; ++i )
	{
		const SSession* pSession = &m_sessions[ i ];

		if ( (pSession->localFlags & CRYSESSION_LOCAL_FLAG_USED) && ( pSession->createFlags & CRYSESSION_CREATE_FLAG_INVITABLE ) )
		{
			CRY_ASSERT_MESSAGE( ( handle == CrySessionInvalidHandle ), "Multiple matchmaking sessions created with CRYSESSION_CREATE_FLAG_INVITABLE" );
			handle = i;
		}
	}

	return handle;
}


void CCryGameSpyMatchMaking::ClearSessionSearch( SSearch* pSearch )
{
	uint32 i = 0;

	while ( i < MAX_GAMESPY_SEARCH_SERVERS )
	{
		ClearSessionSearchServer( &pSearch->servers[ i ] );
		++i;
	}

	pSearch->used = false;
	pSearch->finished = false;
	pSearch->numExpectedServers = MAX_GAMESPY_SEARCH_SERVERS;
	pSearch->numServersAdded = 0;
	pSearch->numServers = 0;
}


void CCryGameSpyMatchMaking::FreeSessionSearch( SSearch* pSearch )
{
	uint32 i = 0;

	while ( i < pSearch->numServers )
	{
		FreeSessionSearchServer( &pSearch->servers[ i ] );
		++i;
	}

	while ( i < MAX_GAMESPY_SEARCH_SERVERS )
	{
		ClearSessionSearchServer( &pSearch->servers[ i ] );
		++i;
	}

	pSearch->used = false;
	pSearch->finished = false;
	pSearch->numExpectedServers = MAX_GAMESPY_SEARCH_SERVERS;
	pSearch->numServersAdded = 0;
	pSearch->numServers = 0;
}


void CCryGameSpyMatchMaking::ClearSessionSearchServer( SSearch::SServer* pServer )
{
	pServer->server = NULL;
	pServer->sessionPingState = eSPS_NotStarted;
	pServer->firstPingSentTime = CTimeValue();
	pServer->lastPingSentTime = CTimeValue();
	pServer->result.m_id = NULL;
	pServer->result.m_data.m_name[ 0 ] = '\0';
	pServer->result.m_data.m_numPrivateSlots = 0;
	pServer->result.m_data.m_numPublicSlots = 0;
	pServer->result.m_data.m_ranked = false;
	pServer->result.m_data.m_numData = 0;
	pServer->result.m_data.m_data = NULL;
	pServer->result.m_numFilledSlots = 0;
	pServer->result.m_numFriends = 0;
	pServer->result.m_ping = 0;
	pServer->result.m_flags = 0;
	pServer->numMigrations = 0;
	pServer->mode = eGM_OpenStaging;
	pServer->dedicated = false;
}


void CCryGameSpyMatchMaking::FreeSessionSearchServer( SSearch::SServer* pServer )
{
	SAFE_DELETE_ARRAY( pServer->result.m_data.m_data );
	ClearSessionSearchServer( pServer );
}


void CCryGameSpyMatchMaking::MoveSessionSearchServer( SSearch::SServer* pDstServer, SSearch::SServer* pSrcServer )
{
	if ( pDstServer != pSrcServer )
	{
		FreeSessionSearchServer( pDstServer );
		pDstServer->server = pSrcServer->server;
		pDstServer->sessionPingState = pSrcServer->sessionPingState;
		pDstServer->firstPingSentTime = pSrcServer->firstPingSentTime;
		pDstServer->lastPingSentTime = pSrcServer->lastPingSentTime;
		pDstServer->result.m_id = pSrcServer->result.m_id;
		memcpy( pDstServer->result.m_data.m_name, pSrcServer->result.m_data.m_name, sizeof ( pSrcServer->result.m_data.m_name ) );
		pDstServer->result.m_data.m_numPrivateSlots = pSrcServer->result.m_data.m_numPrivateSlots;
		pDstServer->result.m_data.m_numPublicSlots = pSrcServer->result.m_data.m_numPublicSlots;
		pDstServer->result.m_data.m_ranked = pSrcServer->result.m_data.m_ranked;
		pDstServer->result.m_data.m_numData = pSrcServer->result.m_data.m_numData;
		pDstServer->result.m_data.m_data = pSrcServer->result.m_data.m_data;
		pDstServer->result.m_numFilledSlots = pSrcServer->result.m_numFilledSlots;
		pDstServer->result.m_numFriends = pSrcServer->result.m_numFriends;
		pDstServer->result.m_ping = pSrcServer->result.m_ping;
		pDstServer->result.m_flags = pSrcServer->result.m_flags;
		pDstServer->numMigrations = pSrcServer->numMigrations;
		pDstServer->mode = pSrcServer->mode;
		pDstServer->dedicated = pSrcServer->dedicated;
		ClearSessionSearchServer( pSrcServer );
	}
}

void CCryGameSpyMatchMaking::OnGameSpyObjectAvailable( EGameSpyObjectBit object )
{
	for ( CryMatchMakingTaskID id = 0; id < MAX_MATCHMAKING_TASKS; ++id )
	{
		STask*	pTask = &m_task[ id ];

		if ( pTask->used )
		{
			pTask->haveGameSpyObjects |= object;
		}
	}
}


void CCryGameSpyMatchMaking::OnGameSpyObjectUnavailable( EGameSpyObjectBit object )
{
	for ( CryMatchMakingTaskID id = 0; id < MAX_MATCHMAKING_TASKS; ++id )
	{
		STask*	pTask = &m_task[ id ];

		if ( pTask->used )
		{
			pTask->haveGameSpyObjects &= ~object;
		}
	}
}


void CCryGameSpyMatchMaking::OnGameSpyObjectCantMakeAvailable( EGameSpyObjectBit object )
{
	for ( CryMatchMakingTaskID id = 0; id < MAX_MATCHMAKING_TASKS; ++id )
	{
		STask*	pTask = &m_task[ id ];

		if ( pTask->used )
		{
			if ( ( pTask->oldGameSpyObjects | pTask->newGameSpyObjects ) & object )
			{
				UpdateTaskError( id, eCLE_UserNotSignedIn );
			}
		}
	}
}


bool CCryGameSpyMatchMaking::CreateTaskLink( CryMatchMakingTaskID mmTaskID )
{
	bool result = false;
	STask* pTask = &m_task[ mmTaskID ];

	pTask->pLink = new STaskLink();

	if ( pTask->pLink )
	{
		pTask->pLink->pTask = pTask;
		pTask->pLink->pLobby = m_lobby;
		pTask->pLink->pLobbyService = GetService();
		pTask->pLink->pMatchMaking = this;
		pTask->pLink->AddToVector( m_taskLinks );
		result = true;
	}
	else
	{
		UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
	}

	return result;
}


void CCryGameSpyMatchMaking::FreeTaskLinkFromCryNetwork( STask* pTask )
{
	if ( pTask && pTask->pLink )
	{
		pTask->pLink->pTask = NULL;
		pTask->pLink->pLobby = NULL;
		pTask->pLink->pLobbyService = NULL;
		pTask->pLink->pMatchMaking = NULL;
		pTask->pLink = NULL;
	}
}


void CCryGameSpyMatchMaking::FreeTaskLinkFromGameSpy( STaskLink* pLink )
{
	if ( pLink )
	{
		pLink->pLobby = NULL;
		pLink->pLobbyService = NULL;
		pLink->pMatchMaking = NULL;

		if ( pLink->pTask )
		{
			pLink->pTask->pLink = NULL;
			pLink->pTask = NULL;
		}

		pLink->RemoveFromVector();
	}
}


void CCryGameSpyMatchMaking::InitializeTaskGameSpyObjects( STask* pTask, uint8 newObjects, uint8 oldObjects )
{
	CCryGameSpyLobbyService*	pService = GetService();

	pTask->newGameSpyObjects = newObjects;
	pTask->oldGameSpyObjects = oldObjects;
	pTask->haveGameSpyObjects = pService->GetAvailableGameSpyObjects();

	if ( pTask->newGameSpyObjects & eGSOB_Peer )
	{
		pService->SetNeedPeer( true );
	}

	if ( pTask->newGameSpyObjects & eGSOB_GPConnection )
	{
		pService->SetNeedGPConnection( true );
	}

	if ( pTask->newGameSpyObjects & eGSOB_CDKey )
	{
		pService->SetNeedCDKey( true );
	}
}


ICryGameSpyLobbyComponent::ETaskGameSpyCheckResult CCryGameSpyMatchMaking::CheckTaskGameSpyObjects( CryMatchMakingTaskID mmTaskID )
{
	ETaskGameSpyCheckResult result = eTGSCR_Wait;
	STask* pTask = &m_task[ mmTaskID ];

	if ( ( pTask->haveGameSpyObjects & pTask->oldGameSpyObjects ) != pTask->oldGameSpyObjects )
	{
		UpdateTaskError( mmTaskID, eCLE_UserNotSignedIn );
		result = eTGSCR_Fail;
	}
	else
	{
		uint32 newObjects = pTask->newGameSpyObjects & pTask->haveGameSpyObjects;

		pTask->newGameSpyObjects &= ~newObjects;
		pTask->oldGameSpyObjects |= newObjects;

		if ( !pTask->newGameSpyObjects )
		{
			result = eTGSCR_Proceed;
		}
	}

	return result;
}

ECryLobbyError CCryGameSpyMatchMaking::StartTaskSearching( CryMatchMakingTaskID mmTaskID, const unsigned char* pFields, int numFields, const gsi_char* pFilter, peerListingGamesCallback callback, void* pParam )
{
	STask*		pTask = &m_task[ mmTaskID ];
	ECryLobbyError result = eCLE_Success;

	if ( !pTask->searching )
	{
		for ( uint32 i = 0; i < MAX_GAMESPY_SEARCHES; ++i )
		{
			InvalidateSessionSearchSBServers( &m_search[ i ] );
		}

		result = GetService()->PeerStartListingGames( pFields, numFields, pFilter, callback, pParam );
		if (result == eCLE_Success)
		{
			pTask->searching = true;
		}
	}

	return result;
}

void CCryGameSpyMatchMaking::StopTaskSearching( CryMatchMakingTaskID mmTaskID )
{
	STask*		pTask = &m_task[ mmTaskID ];

	if ( pTask->searching )
	{
		GetService()->PeerStopListingGames();
		pTask->searching = false;
	}
}

void CCryGameSpyMatchMaking::InvalidateSessionSearchSBServers( SSearch* pSearch )
{
	for ( uint32 i = 0; i < pSearch->numServers; ++i )
	{
		pSearch->servers[ i ].server = NULL;
	}
}


void CCryGameSpyMatchMaking::SessionEnsureCorrectStagingRoom( CrySessionHandle localSessionIndex )
{
	LOBBY_AUTO_LOCK;

	ECryLobbyError				error;
	CryMatchMakingTaskID	mmTaskID;

	error = StartTask( eT_SessionEnsureCorrectStagingRoom, &mmTaskID, NULL, localSessionIndex, NULL, NULL );

	if ( error == eCLE_Success )
	{
		error = CreateTaskParam( mmTaskID, ENSURE_CORRECT_STAGING_ROOM_PARAM, NULL, 1, sizeof ( SEnsureCorrectStagingRoomParam ) );

		if ( error == eCLE_Success )
		{
			STask*													pTask = &m_task[ mmTaskID ];
			SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( m_lobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

			pParam->flags = 0;
			pParam->server = NULL;
			FROM_GAME_TO_LOBBY( &CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID );
		}
	}
}

void CCryGameSpyMatchMaking::StartSessionEnsureCorrectStagingRoom( CryMatchMakingTaskID mmTaskID )
{
	STask*													pTask = &m_task[ mmTaskID ];
	SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( m_lobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

	if ( CreateTaskLink( mmTaskID ) )
	{
		InitializeTaskGameSpyObjects( pTask, 0, eGSOB_Peer );
	}
	else
	{
		UpdateTaskError( mmTaskID, eCLE_OutOfMemory );
	}
}

void CCryGameSpyMatchMaking::TickSessionEnsureCorrectStagingRoom( CryMatchMakingTaskID mmTaskID )
{
	STask*													pTask = &m_task[ mmTaskID ];
	SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( m_lobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

	if ( pTask->error == eCLE_Success )
	{
		if ( pTask->session == CrySessionInvalidHandle )
		{
			if ( !( pParam->flags & eECSRF_StartedLeave ) )
			{
				GetService()->PeerLeaveStagingRoom( SessionEnsureCorrectStagingRoomPeerLeaveRoomCallback, pTask->pLink );
				pParam->flags |= eECSRF_StartedLeave;
			}

			if ( pParam->flags & eECSRF_FinishedLeave )
			{
				StopTaskRunning( mmTaskID );
			}
		}
		else
		{
			SSession*												pSession = &m_sessions[ pTask->session ];

			if ( !( pParam->flags & eECSRF_StartedJoin ) )
			{
				if ( !pParam->server )
				{
					if ( !pTask->searching )
					{
						if ( pSession->id.m_sid != GAMESPY_INVALID_SESSION_ID )
						{
							char		sid[ 32 ];
							char		filter[ 32 ];

							EncodeU64( pSession->id.m_sid, sid );
							sprintf_s( filter, sizeof ( filter ), "%s='%s'", k_sessionIDKeyName, sid );
							StartTaskSearching( mmTaskID, m_serverSearchQueryKeys, m_numServerSearchQueryKeys, filter, SessionEnsureCorrectStagingRoomPeerListingGamesCallback, pTask->pLink );
						}
					}
				}

				if ( ( pParam->flags & eECSRF_FinishedSearch ) || pParam->server )
				{
					StopTaskSearching( mmTaskID );
				}

				if ( ( pParam->flags & eECSRF_FinishedSearch ) && !pParam->server )
				{
					m_sessions[pTask->session].specialFlags = GS_SPECIALFLAG_FAILED_TO_JOIN_STAGING_ROOM;
					UpdateTaskError( mmTaskID, eCLE_ServerUnreachable );
				}

				if ( pParam->server )
				{
					GetService()->PeerJoinStagingRoom( pParam->server, NULL, SessionEnsureCorrectStagingRoomPeerJoinRoomCallback, pTask->pLink );
					pParam->flags |= eECSRF_StartedJoin;
				}
			}

			if ( pParam->flags & eECSRF_FinishedJoin )
			{
				StopTaskRunning( mmTaskID );
			}
		}
	}

	if ( pTask->error != eCLE_Success )
	{
		StopTaskSearching( mmTaskID );

		if ( !( pParam->flags & ( eECSRF_StartedJoin | eECSRF_StartedLeave ) ) )
		{
			FreeTaskLinkFromGameSpy( pTask->pLink );
		}
	}
}

void CCryGameSpyMatchMaking::SessionEnsureCorrectStagingRoomPeerJoinRoomCallback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void* pParam )
{
	STaskLink*							pLink = static_cast< STaskLink* >( pParam );

	if ( pLink )
	{
		STask*									pTask = pLink->pTask;

		if ( pTask )
		{
			CCryLobby*							pLobby = pLink->pLobby;
			SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( pLobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

			if ( success )
			{
				pParam->flags |= eECSRF_FinishedJoin;
			}
			else
			{
				pLink->pMatchMaking->m_sessions[pTask->session].specialFlags = GS_SPECIALFLAG_FAILED_TO_JOIN_STAGING_ROOM;
				pTask->error = eCLE_ConnectionFailed;
			}
		}

		FreeTaskLinkFromGameSpy( pLink );
	}
}

void CCryGameSpyMatchMaking::SessionEnsureCorrectStagingRoomPeerLeaveRoomCallback( void* pParam )
{
	STaskLink*							pLink = static_cast< STaskLink* >( pParam );

	if ( pLink )
	{
		STask*									pTask = pLink->pTask;

		if ( pTask )
		{
			CCryLobby*							pLobby = pLink->pLobby;
			SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( pLobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

			pParam->flags |= eECSRF_FinishedLeave;
		}

		FreeTaskLinkFromGameSpy( pLink );
	}
}

void CCryGameSpyMatchMaking::SessionEnsureCorrectStagingRoomPeerListingGamesCallback( PEER peer, PEERBool success, const gsi_char* pName, SBServer server, PEERBool staging, int msg, int progress, void* pParam )
{
	STaskLink*											pLink = static_cast< STaskLink* >( pParam );

	if ( pLink )
	{
		STask*													pTask = pLink->pTask;

		if ( pTask )
		{
			SSession*												pSession = &pLink->pMatchMaking->m_sessions[ pTask->session ];
			SEnsureCorrectStagingRoomParam*	pParam = static_cast< SEnsureCorrectStagingRoomParam* >( pLink->pLobby->MemGetPtr( pTask->params[ ENSURE_CORRECT_STAGING_ROOM_PARAM ] ) );

			if(success)
			{
				switch ( msg )
				{
					case PEER_ADD:
					case PEER_UPDATE:

						{
							const char*						pSessionIDStr = SBServerGetStringValue( server, k_sessionIDKeyName, "" );
							uint64								sessionID;
							uint32								addr;
							uint16								port;

							DecodeU64( sessionID, pSessionIDStr );
							addr = SBServerGetPublicInetAddress( server );
							port = htons( SBServerGetPublicQueryPort( server ) );

							if ( ( sessionID == pSession->id.m_sid ) && ( addr == pSession->id.m_addr.m_publicIP ) && ( port == pSession->id.m_addr.m_publicPort ) )
							{
								pParam->server = server;
							}
							else
							{
								if ( pParam->server == server )
								{
									pParam->server = NULL;
								}
							}
						}

						break;

					case PEER_REMOVE:

						if ( server == pParam->server )
						{
							pParam->server = NULL;
						}

						break;

					case PEER_CLEAR:

						pParam->server = NULL;
						break;

					case PEER_COMPLETE:

						pParam->flags |= eECSRF_FinishedSearch;
						break;
				}
			}
			else
			{
				pParam->flags |= eECSRF_FinishedSearch;
				pLink->pMatchMaking->UpdateTaskError(static_cast<CryMatchMakingTaskID>(pTask - pLink->pMatchMaking->m_task), eCLE_InternalError);
			}
		}
	}
}

void CCryGameSpyMatchMaking::DispatchRoomOwnerChangedEvent(TMemHdl mh)
{
	UCryLobbyEventData eventData;
	eventData.pRoomOwnerChanged = (SCryLobbyRoomOwnerChanged*)m_lobby->MemGetPtr(mh);

	m_lobby->DispatchEvent(eCLSE_RoomOwnerChanged, eventData);

	m_lobby->MemFree(mh);
}

#if GAMESPY_ENABLE_SESSION_PING

void CCryGameSpyMatchMaking::ProcessSessionPing( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	if ( pPacket->StartRead() )
	{
		uint8			remoteMmTaskID = pPacket->ReadUINT8();
		uint64		sessionID = pPacket->ReadUINT64();
		bool			sessionFound = false;
		bool			sessionValid = false;

		for ( uint32 i = 0; ( i < MAX_MATCHMAKING_SESSIONS ) && !sessionFound; ++i )
		{
			SSession*	pSession = &m_sessions[ i ];

			if ( pSession->id.m_sid == sessionID )
			{
				sessionFound = true;

				if ( ( pSession->createFlags & CRYSESSION_CREATE_FLAG_NUB ) && ( pSession->createFlags & CRYSESSION_CREATE_FLAG_SEARCHABLE ) && ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_USED ) && ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST ) )
				{
					sessionValid = true;
				}
			}
		}

		CCrySharedLobbyPacket	reply;

		if ( reply.CreateWriteBuffer( CryLobbyPacketHeaderSize + CryLobbyPacketUINT8Size + CryLobbyPacketUINT64Size + CryLobbyPacketBoolSize ) )
		{
			reply.StartWrite( eGameSpyPT_SessionPong, false );
			reply.WriteUINT8( remoteMmTaskID );
			reply.WriteUINT64( sessionID );
			reply.WriteBool( sessionValid );
			Send( CryMatchMakingInvalidTaskID, &reply, CrySessionInvalidHandle, addr );
		}
	}
}

void CCryGameSpyMatchMaking::ProcessSessionPong( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	if ( pPacket->StartRead() )
	{
		CryMatchMakingTaskID	mmTaskID = pPacket->ReadUINT8();
		uint64								sessionID = pPacket->ReadUINT64();
		bool									sessionValid = pPacket->ReadBool();
		STask*								pTask = &m_task[ mmTaskID ];

		if ( pTask->used && pTask->running && ( pTask->startedTask == eT_SessionSearch ) )
		{
			SSearch*							pSearch = &m_search[ pTask->session ];

			for ( uint32 i = 0; i < pSearch->numServers; ++i )
			{
				SSearch::SServer*			pServer = &pSearch->servers[ i ];
				SCryGameSpySessionID*	pId = static_cast< SCryGameSpySessionID* >( pServer->result.m_id.get() );

				if ( pId->m_sid == sessionID )
				{
					if ( pServer->sessionPingState == eSPS_SentPing )
					{
						if ( sessionValid )
						{
							pServer->sessionPingState = eSPS_GotPongValidSession;
						}
						else
						{
							pServer->sessionPingState = eSPS_GotPongInvalidSession;
						}
					}

					break;
				}
			}
		}
	}
}

#endif

void CCryGameSpyMatchMaking::AdjustSessionFriendCount( const char* pSessionURL, int32 delta )
{
	char									title[ GAMESPY_MAX_TITLE_LEN + 1 ];
	uint64								sid;
	uint16								ip0;
	uint16								ip1;
	uint16								ip2;
	uint16								ip3;
	uint32								ip;
	uint16								port;

	if ( sscanf( pSessionURL, "%31[^:]://%3hu.%3hu.%3hu.%3hu:%5hu/%16llX", title, &ip0, &ip1, &ip2, &ip3, &port, &sid ) == 7 )
	{
		SConfigurationParams	info[ 1 ] =
		{
			{ CLCC_GAMESPY_TITLE,				{ NULL } }
		};

		m_lobby->GetConfigurationInformation( info, 1 );

		if ( !strcmp( title, ( const char* )info[ 0 ].m_pData ) )
		{
			( ( uint8* )&ip )[ 0 ] = ( uint8 )ip0;
			( ( uint8* )&ip )[ 1 ] = ( uint8 )ip1;
			( ( uint8* )&ip )[ 2 ] = ( uint8 )ip2;
			( ( uint8* )&ip )[ 3 ] = ( uint8 )ip3;
			port = htons( port );

			for ( uint32 i = 0; i < MAX_GAMESPY_SEARCHES; ++i )
			{
				SSearch*							pSearch = &m_search[ i ];

				for ( uint32 j = 0; j < m_search[ i ].numServers; ++j )
				{
					SSearch::SServer*			pServer = &pSearch->servers[ i ];
					SCryGameSpySessionID*	pID = static_cast< SCryGameSpySessionID* >( pServer->result.m_id.get() );

					if ( ( pID->m_sid == sid ) && ( pID->m_addr.m_publicIP == ip ) && ( pID->m_addr.m_publicPort == port ) )
					{
						pServer->result.m_numFriends += delta;
					}
				}
			}
		}
	}
}

void CCryGameSpyMatchMaking::CreateSessionURL( uint32 ip, uint16 port, uint64 sid, CryFixedStringT< GAMESPY_MAX_SESSION_URL_SIZE >& url )
{
	SConfigurationParams	info[ 1 ] =
	{
		{ CLCC_GAMESPY_TITLE,				{ NULL } }
	};

	m_lobby->GetConfigurationInformation( info, 1 );
	url.Format( "%s://%d.%d.%d.%d:%d/%llX", ( const char* )info[ 0 ].m_pData, ( ( uint8* )&ip )[ 0 ], ( ( uint8* )&ip )[ 1 ], ( ( uint8* )&ip )[ 2 ], ( ( uint8* )&ip )[ 3 ], ntohs( port ), sid );
}

void CCryGameSpyMatchMaking::EncodeU8( uint8 val, char* pStr )
{
	EncodeU( val, pStr, GAMESPY_ENCODE8BIT_INITIAL_DIVISOR );
}

void CCryGameSpyMatchMaking::EncodeU16( uint16 val, char* pStr )
{
	EncodeU( val, pStr, GAMESPY_ENCODE16BIT_INITIAL_DIVISOR );
}

void CCryGameSpyMatchMaking::EncodeU32( uint32 val, char* pStr )
{
	EncodeU( val, pStr, GAMESPY_ENCODE32BIT_INITIAL_DIVISOR );
}

void CCryGameSpyMatchMaking::EncodeU64( uint64 val, char* pStr )
{
	EncodeU( val, pStr, GAMESPY_ENCODE64BIT_INITIAL_DIVISOR );
}

void CCryGameSpyMatchMaking::EncodeU( uint64 val, char* pStr, uint64 divisor )
{
	char*									pWrite = pStr;

	while ( divisor )
	{
		if ( ( val / divisor ) || ( pWrite > pStr ) || ( divisor == 1 ) )
		{
			*pWrite = k_encodeNumericKey[ val / divisor ];
			++pWrite;
		}

		val %= divisor;
		divisor /= GAMESPY_ENCODE_NUMERIC_TABLE_SIZE;
	}

	*pWrite = '\0';
}

void CCryGameSpyMatchMaking::DecodeU8( uint8& val, const char* pStr )
{
	uint64	val64 = 0;

	DecodeU64( val64, pStr );
	val = ( uint8 )val64;
}

void CCryGameSpyMatchMaking::DecodeU16( uint16& val, const char* pStr )
{
	uint64	val64 = 0;

	DecodeU64( val64, pStr );
	val = ( uint16 )val64;
}

void CCryGameSpyMatchMaking::DecodeU32( uint32& val, const char* pStr )
{
	uint64	val64 = 0;

	DecodeU64( val64, pStr );
	val = ( uint32 )val64;
}

void CCryGameSpyMatchMaking::DecodeU64( uint64& val, const char* pStr )
{
	val = 0;

	while ( *pStr )
	{
		val *= ( uint64 )GAMESPY_ENCODE_NUMERIC_TABLE_SIZE;
		val += ( uint64 )k_decodeNumericKey[ *pStr ];
		++pStr;
	}
}

void CCryGameSpyMatchMaking::EncodeS8( int8 val, char* pStr )
{
	EncodeU8( reinterpret_cast< uint8& >( val ), pStr );
}

void CCryGameSpyMatchMaking::EncodeS16( int16 val, char* pStr )
{
	EncodeU16( reinterpret_cast< uint16& >( val ), pStr );
}

void CCryGameSpyMatchMaking::EncodeS32( int32 val, char* pStr )
{
	EncodeU32( reinterpret_cast< uint32& >( val ), pStr );
}

void CCryGameSpyMatchMaking::EncodeS64( int64 val, char* pStr )
{
	EncodeU64( reinterpret_cast< uint64& >( val ), pStr );
}

void CCryGameSpyMatchMaking::DecodeS8( int8& val, const char* pStr )
{
	DecodeU8( reinterpret_cast< uint8& >( val ), pStr );
}

void CCryGameSpyMatchMaking::DecodeS16( int16& val, const char* pStr )
{
	DecodeU16( reinterpret_cast< uint16& >( val ), pStr );
}

void CCryGameSpyMatchMaking::DecodeS32( int32& val, const char* pStr )
{
	DecodeU32( reinterpret_cast< uint32& >( val ), pStr );
}

void CCryGameSpyMatchMaking::DecodeS64( int64& val, const char* pStr )
{
	DecodeU64( reinterpret_cast< uint64& >( val ), pStr );
}

ECryLobbyError CCryGameSpyMatchMaking::SessionSendRequestInfo(CCryLobbyPacket* pPacket, CrySessionID id, CryLobbyTaskID* pTaskID, CryMatchmakingSessionSendRequestInfoCallback pCB, void* pCBArg)
{
#if !defined(DEDICATED_SERVER)
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;

	if (pPacket && (id != CrySessionInvalidID))
	{
		CryMatchMakingTaskID mmTaskID;
		
		error = StartTask(eT_SessionSendRequestInfo, &mmTaskID, pTaskID, CrySessionInvalidHandle, pCB, pCBArg);

		if (error == eCLE_Success)
		{
			SCryGameSpySessionID* pSessionID = (SCryGameSpySessionID*)id.get();

			error = CreateTaskParam(mmTaskID, TDM_SEND_REQUEST_INFO_SESSIONID, pSessionID, 0, sizeof(SCryGameSpySessionID));

			if (error == eCLE_Success)
			{
				STask* pTask = &m_task[mmTaskID];
				uint32 bufferEndPos = pPacket->GetReadBufferPos();
				uint32 userPacketType = pPacket->StartRead();
				uint32 bufferStartPos = pPacket->GetReadBufferPos();
				uint32 userDataSize = bufferEndPos - bufferStartPos;
				uint32 writePacketSize = CryLobbyPacketUnReliableHeaderSize + CryLobbyPacketUINT64Size + CryLobbyPacketUINT32Size + CryLobbyPacketUINT8Size + CryLobbyPacketUINT8Size + CryLobbyPacketUINT16Size + userDataSize;
				CCryLobbyPacket packet;

				if (packet.CreateWriteBuffer(writePacketSize))
				{
					pTask->numParams[TDN_SEND_REQUEST_INFO_COOKIE] = CreateNATCookie(&pSessionID->m_addr);
					pTask->numParams[TDN_SEND_REQUEST_INFO_SEND_TRY] = 0;

					// Create an unreliable packet to send the request.
					packet.StartWrite(eGameSpyPT_SessionRequestInfo, false);
					packet.WriteUINT64(pSessionID->m_sid);
					packet.WriteUINT32(pTask->numParams[TDN_SEND_REQUEST_INFO_COOKIE]);
					packet.WriteUINT8(mmTaskID);
					packet.WriteUINT8(userPacketType);
					packet.WriteUINT16(userDataSize);

					if (userDataSize > 0)
					{
						packet.WriteData(pPacket->GetReadBuffer() + bufferStartPos, userDataSize);
					}

					pTask->params[TDM_SEND_REQUEST_INFO_PACKET] = PacketToMemoryBlock(&packet, pTask->numParams[TDN_SEND_REQUEST_INFO_PACKET_SIZE]);

					if (pTask->params[TDM_SEND_REQUEST_INFO_PACKET] != TMemInvalidHdl)
					{
						FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::StartTaskRunning, this, mmTaskID);
					}
					else
					{
						error = eCLE_OutOfMemory;
					}

					packet.FreeWriteBuffer();
				}
				else
				{
					error = eCLE_OutOfMemory;
				}
			}

			if (error != eCLE_Success)
			{
				FreeTask(mmTaskID);
			}
		}
	}
	else
	{
		error = eCLE_InvalidParam;
	}

	return error;
#else
	return eCLE_InvalidRequest;
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::StartSessionSendRequestInfo(CryMatchMakingTaskID mmTaskID)
{
#if !defined(DEDICATED_SERVER)
	STask* pTask = &m_task[mmTaskID];
	SCryGameSpySessionID* pSessionID = (SCryGameSpySessionID*)m_lobby->MemGetPtr(pTask->params[TDM_SEND_REQUEST_INFO_SESSIONID]);

	StartSubTask(eT_SessionSendRequestInfoNAT, mmTaskID);
	StartFindUseAddress(&pSessionID->m_addr, pTask->numParams[TDN_SEND_REQUEST_INFO_COOKIE], 0, true);
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::TickSessionSendRequestInfoNAT(CryMatchMakingTaskID mmTaskID)
{
#if !defined(DEDICATED_SERVER)
	STask* pTask = &m_task[mmTaskID];
	SCryGameSpySessionID* pSessionID = (SCryGameSpySessionID*)m_lobby->MemGetPtr(pTask->params[TDM_SEND_REQUEST_INFO_SESSIONID]);

	switch (pSessionID->m_addr.m_state)
	{
	case eGSNT_Started:
		break;

	case eGSNT_Succeeded:
		StartSubTask(eT_SessionSendRequestInfo, mmTaskID);
		break;

	case eGSNT_Failed:
		UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
		StopTaskRunning(mmTaskID);
		break;
	}
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::TickSessionSendRequestInfo(CryMatchMakingTaskID mmTaskID)
{
#if !defined(DEDICATED_SERVER)
	STask* pTask = &m_task[mmTaskID];
	bool doSend = false;

	if (pTask->TimerStarted())
	{
		if (pTask->GetTimer() > CryLobbySendInterval)
		{
			pTask->StartTimer();
			doSend = true;
		}
	}
	else
	{
		pTask->StartTimer();
		doSend = true;
	}

	if (doSend)
	{
		if (pTask->numParams[TDN_SEND_REQUEST_INFO_SEND_TRY] < SEND_REQUEST_INFO_MAX_SEND_TRYS)
		{
			SCryGameSpySessionID* pSessionID = (SCryGameSpySessionID*)m_lobby->MemGetPtr(pTask->params[TDM_SEND_REQUEST_INFO_SESSIONID]);
			CCryLobbyPacket* pPacket = MemoryBlockToPacket(pTask->params[TDM_SEND_REQUEST_INFO_PACKET], pTask->numParams[TDN_SEND_REQUEST_INFO_PACKET_SIZE]);
			TNetAddress to = TNetAddress(SIPv4Addr(ntohl(pSessionID->m_addr.m_useIP), ntohs(pSessionID->m_addr.m_usePort)));

			pTask->numParams[TDN_SEND_REQUEST_INFO_SEND_TRY]++;

			if (m_lobby->Send(pPacket, to, CryLobbyInvalidConnectionID, NULL) != eSE_Ok)
			{
				UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
			}
		}
		else
		{
			UpdateTaskError(mmTaskID, eCLE_ConnectionFailed);
		}

		if (pTask->error != eCLE_Success)
		{
			StopTaskRunning(mmTaskID);
		}
	}
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::EndSessionSendRequestInfo(CryMatchMakingTaskID mmTaskID)
{
#if !defined(DEDICATED_SERVER)
	STask* pTask = &m_task[mmTaskID];
	CCryLobbyPacket* pPacket = MemoryBlockToPacket(pTask->params[TDM_SEND_REQUEST_INFO_PACKET_RESPONSE], pTask->numParams[TDN_SEND_REQUEST_INFO_PACKET_RESPONSE_SIZE]);

	((CryMatchmakingSessionSendRequestInfoCallback)pTask->cb)(pTask->lTaskID, pTask->error, pPacket, pTask->cbArg);
#endif // !defined(DEDICATED_SERVER)
}

void CCryGameSpyMatchMaking::DispatchSessionRequestInfo(CrySessionHandle gh, TMemHdl packetMH, uint32 length, TMemHdl requesterMH)
{
	UCryLobbyEventData eventData;
	SCryLobbySessionRequestInfo info;
	CCrySharedLobbyPacket* pSPacket = (CCrySharedLobbyPacket*)MemoryBlockToPacket(packetMH, length);

	eventData.pSessionRequestInfo = &info;
	info.pPacket = pSPacket;
	info.session = gh;
	info.requester = m_lobby->MemGetPtr(requesterMH);

	m_lobby->DispatchEvent(eCLSE_SessionRequestInfo, eventData);
	m_lobby->MemFree(packetMH);
	m_lobby->MemFree(requesterMH);
}

void CCryGameSpyMatchMaking::ProcessSessionRequestInfo(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket)
{
	uint64 sid;
	CrySessionHandle h;

	pPacket->StartRead();
	sid = pPacket->ReadUINT64();
	h = FindSessionFromSessionID(sid);

	if (h != CrySessionInvalidHandle)
	{
		uint32 cookie = pPacket->ReadUINT32();
		CryMatchMakingTaskID returnTaskID = pPacket->ReadUINT8();
		uint32 userPacketType = pPacket->ReadUINT8();
		uint32 userDataSize = pPacket->ReadUINT16();
		uint32 writePacketSize = CryLobbyPacketUnReliableHeaderSize + userDataSize;
		CCryLobbyPacket packet;

		// Recreate the packet the user sent.
		if (packet.CreateWriteBuffer(writePacketSize))
		{
			packet.StartWrite(userPacketType, false);

			if (userDataSize > 0)
			{
				packet.WriteData(pPacket->GetReadBuffer() + pPacket->GetReadBufferPos(), userDataSize);
			}

			uint32 length;
			TMemHdl packetMH = PacketToMemoryBlock(&packet, length);

			if (packetMH != TMemInvalidHdl)
			{
				TMemHdl requesterMH = m_lobby->MemAlloc(sizeof(SCrySessionInfoRequester));

				if (requesterMH != TMemInvalidHdl)
				{
					SSession* pSession = &m_sessions[h];
					CrySessionHandle gh = CreateGameSessionHandle(h, pSession->localConnection.uid);
					SCrySessionInfoRequester* pRequester = new (m_lobby->MemGetPtr(requesterMH)) SCrySessionInfoRequester;

					pRequester->addr = addr;
					pRequester->cookie = cookie;
					pRequester->mmTaskID = returnTaskID;

					TO_GAME_FROM_LOBBY(&CCryGameSpyMatchMaking::DispatchSessionRequestInfo, this, gh, packetMH, length, requesterMH);
				}
				else
				{
					m_lobby->MemFree(packetMH);
				}
			}

			packet.FreeWriteBuffer();
		}
	}
}

ECryLobbyError CCryGameSpyMatchMaking::SessionSendRequestInfoResponse(CCryLobbyPacket* pPacket, CrySessionRequesterID requester)
{
#if !defined(PURE_CLIENT)
	LOBBY_AUTO_LOCK;

	ECryLobbyError error = eCLE_Success;

	if (pPacket && (requester != CrySessionInvalidRequesterID))
	{
		uint32 bufferEndPos = pPacket->GetReadBufferPos();
		uint32 userPacketType = pPacket->StartRead();
		uint32 bufferStartPos = pPacket->GetReadBufferPos();
		uint32 userDataSize = bufferEndPos - bufferStartPos;
		uint32 writePacketSize = CryLobbyPacketUnReliableHeaderSize + CryLobbyPacketUINT8Size + CryLobbyPacketUINT32Size + CryLobbyPacketUINT8Size + CryLobbyPacketUINT16Size + userDataSize;
		CCryLobbyPacket packet;

		if (packet.CreateWriteBuffer(writePacketSize))
		{
			TMemHdl requesterMH = m_lobby->MemAlloc(sizeof(SCrySessionInfoRequester));

			if (requesterMH != TMemInvalidHdl)
			{
				SCrySessionInfoRequester* pRequester = new (m_lobby->MemGetPtr(requesterMH)) SCrySessionInfoRequester;

				*pRequester = *(SCrySessionInfoRequester*)requester;

				// Create an unreliable packet to send the request.
				packet.StartWrite(eGameSpyPT_SessionRequestInfoResponse, false);
				packet.WriteUINT8(pRequester->mmTaskID);
				packet.WriteUINT32(pRequester->cookie);
				packet.WriteUINT8(userPacketType);
				packet.WriteUINT16(userDataSize);

				if (userDataSize > 0)
				{
					packet.WriteData(pPacket->GetReadBuffer() + bufferStartPos, userDataSize);
				}

				uint32 length;
				TMemHdl packetMH = PacketToMemoryBlock(&packet, length);

				if (packetMH != TMemInvalidHdl)
				{
					FROM_GAME_TO_LOBBY(&CCryGameSpyMatchMaking::SessionSendRequestInfoResponseNT, this, packetMH, length, requesterMH);
				}
				else
				{
					m_lobby->MemFree(requesterMH);
					error = eCLE_OutOfMemory;
				}
			}
			else
			{
				error = eCLE_OutOfMemory;
			}

			packet.FreeWriteBuffer();
		}
		else
		{
			error = eCLE_OutOfMemory;
		}
	}
	else
	{
		error = eCLE_InvalidParam;
	}

	return error;
#else
	return eCLE_InvalidRequest;
#endif // !defined(PURE_CLIENT)
}

void CCryGameSpyMatchMaking::SessionSendRequestInfoResponseNT(TMemHdl packetMH, uint32 length, TMemHdl requesterMH)
{
#if !defined(PURE_CLIENT)
	CCryLobbyPacket* pPacket = MemoryBlockToPacket(packetMH, length);
	SCrySessionInfoRequester* pRequester = (SCrySessionInfoRequester*)m_lobby->MemGetPtr(requesterMH);

	m_lobby->Send(pPacket, pRequester->addr, CryLobbyInvalidConnectionID, NULL);
	m_lobby->MemFree(packetMH);
	m_lobby->MemFree(requesterMH);
#endif // !defined(PURE_CLIENT)
}

void CCryGameSpyMatchMaking::ProcessSessionRequestInfoResponse(const TNetAddress& addr, CCrySharedLobbyPacket* pPacket)
{
	CryMatchMakingTaskID mmTaskID;

	pPacket->StartRead();
	mmTaskID = pPacket->ReadUINT8();
	mmTaskID = FindTaskFromTaskTaskID(eT_SessionSendRequestInfo, mmTaskID);

	if (mmTaskID != CryMatchMakingInvalidTaskID)
	{
		STask* pTask = &m_task[mmTaskID];
		uint32 cookie = pPacket->ReadUINT32();

		if (pTask->numParams[TDN_SEND_REQUEST_INFO_COOKIE] == cookie)
		{
			uint32 userPacketType = pPacket->ReadUINT8();
			uint32 userDataSize = pPacket->ReadUINT16();
			uint32 writePacketSize = CryLobbyPacketUnReliableHeaderSize + userDataSize;
			CCryLobbyPacket packet;

			// Recreate the packet the user sent.
			if (packet.CreateWriteBuffer(writePacketSize))
			{
				packet.StartWrite(userPacketType, false);

				if (userDataSize > 0)
				{
					packet.WriteData(pPacket->GetReadBuffer() + pPacket->GetReadBufferPos(), userDataSize);
				}

				pTask->params[TDM_SEND_REQUEST_INFO_PACKET_RESPONSE] = PacketToMemoryBlock(&packet, pTask->numParams[TDN_SEND_REQUEST_INFO_PACKET_RESPONSE_SIZE]);

				if (pTask->params[TDM_SEND_REQUEST_INFO_PACKET_RESPONSE] == TMemInvalidHdl)
				{
					UpdateTaskError(mmTaskID, eCLE_OutOfMemory);
				}

				packet.FreeWriteBuffer();
			}
			else
			{
				UpdateTaskError(mmTaskID, eCLE_OutOfMemory);
			}

			StopTaskRunning(mmTaskID);
		}
	}
}

CrySessionID CCryGameSpyMatchMaking::GetSessionIDFromConsole()
{
	CrySessionID					result = CrySessionInvalidID;
	IConsole*							pConsole = gEnv->pConsole;

	if ( pConsole )
	{
		ICVar*								pCVar = pConsole->GetCVar( "cl_serveraddr" );

		if ( pCVar )
		{
			const char*						pAddr = pCVar->GetString();

			GetSessionIDFromIP( pAddr, &result );
		}
	}

	return result;
}

ECryLobbyError CCryGameSpyMatchMaking::GetSessionAddressFromSessionID( CrySessionID sessionID, uint32& hostIP, uint16& hostPort )
{
	ECryLobbyError				result = eCLE_InvalidSession;
	SCryGameSpySessionID*	pID = static_cast< SCryGameSpySessionID* >( sessionID.get() );

	if ( pID )
	{
		hostIP = ntohl( pID->m_addr.m_publicIP );
		hostPort = ntohs( pID->m_addr.m_publicPort );
		result = eCLE_Success;
	}

	return result;
}

ECryLobbyError CCryGameSpyMatchMaking::GetSessionIDFromSessionURL( const char* const pSessionURL, CrySessionID* const pSessionID )
{
	ECryLobbyError				result = eCLE_InvalidParam;
	char									title[ GAMESPY_MAX_TITLE_LEN + 1 ];
	char									addr[ MAX_ASCII_SOCKET_ADDRESS_LEN + 1 ];
	uint64								sid;

	if ( pSessionURL )
	{
		if ( sscanf( pSessionURL, "%31[^:]://%21[.0-9:]/%16llX", title, addr, &sid ) == 3 )
		{
			SConfigurationParams	info[ 1 ] =
			{
				{ CLCC_GAMESPY_TITLE,				{ NULL } }
			};

			m_lobby->GetConfigurationInformation( info, 1 );

			if ( info[ 0 ].m_pData && !strcmp( title, ( const char* )info[ 0 ].m_pData ) )
			{
				result = GetSessionIDFromIP( addr, pSessionID );

				if ( result == eCLE_Success )
				{
					SCryGameSpySessionID*		pID = static_cast< SCryGameSpySessionID* >( pSessionID->get() );

					pID->m_sid = sid;
				}			
			}
		}
	}

	return result;
}

ECryLobbyError CCryGameSpyMatchMaking::GetSessionIDFromIP( const char* const pAddr, CrySessionID* const pSessionID )
{
	ECryLobbyError				error = eCLE_InvalidParam;

	if ( pSessionID )
	{
		CrySessionID					sessionID = CrySessionInvalidID;

		if ( pAddr )
		{
			uint16								ip0;
			uint16								ip1;
			uint16								ip2;
			uint16								ip3;
			uint16								hostPort;

			if ( sscanf( pAddr, "%3hu.%3hu.%3hu.%3hu:%5hu", &ip0, &ip1, &ip2, &ip3, &hostPort ) == 5 )
			{
				if ( ( ip0 < 256 ) && ( ip1 < 256 ) && ( ip2 < 256 ) && ( ip3 < 256 ) )
				{
					SCryGameSpySessionID*	pID = new SCryGameSpySessionID();

					if ( pID )
					{
						uint32								hostIP;
						uint32								netIP;
						uint32								useIP = 0;
						uint32								publicIP = 0;
						uint32								privateIP = 0;
						uint16								netPort;
						uint16								usePort = 0;
						uint16								publicPort = 0;
						uint16								privatePort = 0;

						hostIP = ( ip0 << 24 ) | ( ip1 << 16 ) | ( ip2 << 8 ) | ip3;
						netIP = htonl( hostIP );
						netPort = htons( hostPort );

						// RFC5735 private addresses
						if ( ( ( hostIP & 0xFF000000 ) == 0x0A000000 ) ||	// Private class A	10.0.0.0/8
								 ( ( hostIP & 0xFFFF0000 ) == 0xA9FE0000 ) ||	// Link local				169.254.0.0/16
								 ( ( hostIP & 0xFFF00000 ) == 0xAC100000 ) ||	// Private class B	172.16.0.0/12
								 ( ( hostIP & 0xFFFFFF00 ) == 0xC0000200 ) ||	// TEST-NET-1				192.0.2.0/24
								 ( ( hostIP & 0xFFFF0000 ) == 0xC0A80000 ) ||	// Private class C	192.168.0.0/16
								 ( ( hostIP & 0xFFFFFF00 ) == 0xC6336400 ) ||	// TEST-NET-2				198.51.100.0/24
								 ( ( hostIP & 0xFFFFFF00 ) == 0xCB007100 ) )	// TEST-NET-3				203.0.113.0/24
						{
							useIP = netIP;
							usePort = netPort;
							privateIP = netIP;
							privatePort = netPort;
						}
						else
						{
							publicIP = netIP;
							publicPort = netPort;
						}

						pID->m_addr = CreateRemoteConnectionID( useIP, usePort, publicIP, publicPort, privateIP, privatePort );
						pID->m_sid = GAMESPY_INVALID_SESSION_ID;
						sessionID = pID;
						error = eCLE_Success;
					}
					else
					{
						error = eCLE_OutOfMemory;
					}
				}
			}
		}

		*pSessionID = sessionID;
	}

	return error;
}

void CCryGameSpyMatchMaking::SendCDKeyChallenge( SSession::SRConnection *pConnection, char* pChallenge )
{
	CCrySharedLobbyPacket	packet;
	uint32								maxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT32Size + CryLobbyPacketUINT8Size + GAMESPY_CD_KEY_CHALLENGE_SIZE;

	if ( !pChallenge )
	{
		maxBufferSize += CryLobbyPacketUINT8Size;
	}

	if ( packet.CreateWriteBuffer( maxBufferSize ) )
	{
		packet.StartWrite( eGameSpyPT_CDKeyChallenge, true );
		packet.WriteUINT32( pConnection->m_cdKeyLocalID );

		if ( !pChallenge )
		{
			packet.WriteUINT8( CDResponseMethod_NEWAUTH );
			packet.WriteUINT8( pConnection->m_joinTaskID );
			CreateChallengeString( pConnection->m_cdKeyChallengeString );
		}
		else
		{
			packet.WriteUINT8( CDResponseMethod_REAUTH );
			strncpy( pConnection->m_cdKeyChallengeString, pChallenge, sizeof ( pConnection->m_cdKeyChallengeString ) );
		}

		packet.WriteString( pConnection->m_cdKeyChallengeString, GAMESPY_CD_KEY_CHALLENGE_SIZE );

		if ( m_lobby->Send( &packet, TNetAddress( SIPv4Addr( ntohl( pConnection->id.m_useIP ), ntohs( pConnection->id.m_usePort ) ) ), pConnection->connectionID, NULL ) == eSE_Ok )
		{
			if ( pChallenge )
			{
				pConnection->m_cdKeyChallengeState = eCDKCS_ReauthChallengeSent;
			}
			else
			{
				pConnection->m_cdKeyChallengeState = eCDKCS_AuthChallengeSent;
			}
		}
	}
}

void CCryGameSpyMatchMaking::CreateChallengeString( char* pString )
{
	uint32								randValue;
	uint32								i = 0;
	uint8									base62CharsRemaining = 0;

	while ( i < GAMESPY_CD_KEY_CHALLENGE_SIZE - 1 )
	{
		if ( !base62CharsRemaining )
		{
			rand_s( &randValue );
			randValue = ( uint32 )( ( ( uint64 )randValue * NUM_BASE62STRINGS_PER_UINT32 ) / ( ( uint64 )UINT_MAX + 1ull ) );
			base62CharsRemaining = MAX_BASE62CHARS_PER_UINT32;
		}

		uint8									base62Char = randValue % CD_KEY_CHALLENGE_CHARSET_SIZE;

		randValue /= CD_KEY_CHALLENGE_CHARSET_SIZE;
		--base62CharsRemaining;

		if ( base62Char < FIRST_BASE62_UPPER )
		{
			pString[ i ] = base62Char + '0';
		}
		else
		{
			if ( base62Char < FIRST_BASE62_LOWER )
			{
				pString[ i ] = base62Char + 'A' - FIRST_BASE62_UPPER;
			}
			else
			{
				pString[ i ] = base62Char + 'a' - FIRST_BASE62_LOWER;
			}
		}

		++i;
	}

	pString[ i ] = '\0';
}

void CCryGameSpyMatchMaking::ProcessCDKeyChallenge( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	ECryLobbyError				error = eCLE_Success;
	CryLobbyConnectionID	c;

	if ( m_lobby->ConnectionFromAddress( &c, addr ) )
	{
		CryMatchMakingTaskID	mmTaskID;
		CDResponseMethod			method;
		char									challenge[ GAMESPY_CD_KEY_CHALLENGE_SIZE ];
		char									response[ GAMESPY_CD_KEY_RESPONSE_SIZE ];
		int32									localid;
		CCrySharedLobbyPacket	responsePacket;
		const uint32					maxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketUINT32Size + sizeof ( response );

		if ( responsePacket.CreateWriteBuffer( maxBufferSize ) )
		{
			SConfigurationParams	neededInfo[ 1 ] =
			{
				{ CLCC_GAMESPY_CDKEY,						{ NULL } }
			};

			m_lobby->GetConfigurationInformation( neededInfo, ARRAY_COUNT( neededInfo ) );
			pPacket->StartRead();
			localid = pPacket->ReadUINT32();
			method = ( CDResponseMethod )pPacket->ReadUINT8();

			if ( method == CDResponseMethod_NEWAUTH )
			{
				mmTaskID = pPacket->ReadUINT8();
				mmTaskID = FindTaskFromTaskTaskID( eT_SessionJoin, mmTaskID );
			}
			else
			{
				mmTaskID = CryMatchMakingInvalidTaskID;
			}

			pPacket->ReadString( challenge, sizeof ( challenge ) );
			gcd_compute_response( static_cast< char* >( neededInfo[ 0 ].m_pData ), challenge, response, method );
			responsePacket.StartWrite( eGameSpyPT_CDKeyResponse, true );
			responsePacket.WriteUINT32( localid );
			responsePacket.WriteString( response, GAMESPY_CD_KEY_RESPONSE_SIZE );

			if ( m_lobby->Send( &responsePacket, addr, c, NULL ) != eSE_Ok )
			{
				if ( mmTaskID != CryMatchMakingInvalidTaskID )
				{
					UpdateTaskError( mmTaskID, eCLE_ConnectionFailed );
				}
			}
		}
	}
}

void CCryGameSpyMatchMaking::ProcessCDKeyResponse( const TNetAddress& addr, CCrySharedLobbyPacket* pPacket )
{
	ECryLobbyError							error = eCLE_Success;
	CryLobbyConnectionID				c;

	if ( m_lobby->ConnectionFromAddress( &c, addr ) )
	{
		int32												localid;
		CrySessionHandle						sessionIdx;

		pPacket->StartRead();
		localid = pPacket->ReadUINT32();
		sessionIdx = ( localid & GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_MASK ) >> GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_SHIFT;

		if ( sessionIdx < ARRAY_COUNT( m_sessions ) )
		{
			SSession*										pSession = &m_sessions[ sessionIdx ];
			CryMatchMakingConnectionID	clientIdx = ( localid & GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_MASK ) >> GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_SHIFT;

			if ( clientIdx < ARRAY_COUNT( pSession->remoteConnection ) )
			{
				SSession::SRConnection*			pConnection = &pSession->remoteConnection[ clientIdx ];

				if ( pConnection->used && ( pConnection->connectionID == c ) && ( pConnection->m_cdKeyLocalID == localid ) )
				{
					char												response[ GAMESPY_CD_KEY_RESPONSE_SIZE ];

					pPacket->ReadString( response, sizeof ( response ) );

					switch ( pConnection->m_cdKeyChallengeState )
					{
					case eCDKCS_AuthChallengeSent:
						gcd_authenticate_user( GetService()->GetGameID(), pConnection->m_cdKeyLocalID, pConnection->id.m_publicIP, pConnection->m_cdKeyChallengeString, response, CDKeyAuthCallback, CDKeyReauthCallback, this );
						pConnection->m_cdKeyChallengeState = eCDKCS_AuthValidationRequested;
						break;
					case eCDKCS_ReauthChallengeSent:
						gcd_process_reauth( GetService()->GetGameID(), pConnection->m_cdKeyLocalID, pConnection->m_cdKeyReauthHint, response );
						pConnection->m_cdKeyChallengeState = eCDKCS_ReauthValidationRequested;
						break;
					default:
						break;
					}
				}
			}
		}
	}
}

void CCryGameSpyMatchMaking::CDKeyAuthCallback( int gameid, int localid, int authenticated, char* pErrMsg, void* pParam )
{
	CCryGameSpyMatchMaking*	pThis = static_cast< CCryGameSpyMatchMaking* >( pParam );
	CrySessionHandle				sessionIdx = ( localid & GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_MASK ) >> GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_SHIFT;

	if ( sessionIdx < MAX_MATCHMAKING_SESSIONS )
	{
		SSession*								pSession = &pThis->m_sessions[ sessionIdx ];

		if ( (pSession->localFlags & (CRYSESSION_LOCAL_FLAG_HOST | CRYSESSION_LOCAL_FLAG_USED)) == (CRYSESSION_LOCAL_FLAG_HOST | CRYSESSION_LOCAL_FLAG_USED) )
		{
			uint32									clientIdx = ( localid & GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_MASK ) >> GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_SHIFT;
			SSession::SRConnection*	pConnection = &pSession->remoteConnection[ clientIdx ];

			if ( ( pConnection->used ) && ( pConnection->m_cdKeyLocalID == localid ) )
			{
				if ( authenticated )
				{
					switch ( pConnection->m_cdKeyChallengeState )
					{
					case eCDKCS_AuthValidationRequested:
						pThis->SessionJoinSuccess( pSession, clientIdx, pConnection->m_joinTaskID, sessionIdx );
						break;
					case eCDKCS_ReauthValidationRequested:
						pConnection->m_cdKeyChallengeState = eCDKCS_ValidationSucceeded;
						break;
					}
				}
				else
				{
					// Arrr, shiver me timbers!
					switch ( pConnection->m_cdKeyChallengeState )
					{
					case eCDKCS_AuthValidationRequested:
						pThis->SessionJoinFail( pSession->id.m_sid, TranslateCDKeyError( pErrMsg ), pConnection->m_joinTaskID, TNetAddress( SIPv4Addr( ntohl( pConnection->id.m_useIP ), ntohs( pConnection->id.m_usePort ) ) ) );
						break;
					case eCDKCS_ReauthValidationRequested:
						pThis->SessionDisconnectRemoteConnectionViaNub( sessionIdx, clientIdx, eDC_AuthenticationFailed, pErrMsg, true );
						break;
					}
				}
			}
		}
	}
}

void CCryGameSpyMatchMaking::CDKeyReauthCallback( int gameid, int localid, int hint, char* pChallenge, void* pParam )
{
	CCryGameSpyMatchMaking*			pThis = static_cast< CCryGameSpyMatchMaking* >( pParam );
	CrySessionHandle						sessionIdx = ( localid & GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_MASK ) >> GAMESPY_CD_KEY_LOCALID_SESSION_INDEX_SHIFT;

	if ( sessionIdx < MAX_MATCHMAKING_SESSIONS )
	{
		SSession*										pSession = &pThis->m_sessions[ sessionIdx ];

		if ( ( pSession->createFlags & CRYSESSION_LOCAL_FLAG_USED ) && ( pSession->localFlags & CRYSESSION_LOCAL_FLAG_HOST ) )
		{
			CryMatchMakingConnectionID	clientIdx = ( localid & GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_MASK ) >> GAMESPY_CD_KEY_LOCALID_LOBBYCONNECTION_SHIFT;
			SSession::SRConnection*			pConnection = &pSession->remoteConnection[ clientIdx ];

			if ( ( pConnection->used ) && ( pConnection->m_cdKeyLocalID == localid ) )
			{
				pThis->SendCDKeyChallenge( pConnection, pChallenge );
			}
		}
	}
}

ECryLobbyError CCryGameSpyMatchMaking::SessionJoinSuccess( SSession* pSession, CryMatchMakingConnectionID id, CryMatchMakingTaskID returnTaskID, CrySessionHandle localSessionIndex ) 
{
	ECryLobbyError	error = eCLE_Success;

	// Added to session
	CCrySharedLobbyPacket	packet;

	if ( packet.CreateWriteBuffer( MAX_LOBBY_PACKET_SIZE ) )
	{
		SSession::SRConnection* pConnection = &pSession->remoteConnection[id];
		uint32 cookies[MAX_LOBBY_CONNECTIONS];

		packet.StartWrite(eGameSpyPT_SessionRequestJoinResult, true);
		packet.WriteError(eCLE_Success);
		packet.WriteUINT8(returnTaskID);
		// Client might not have our public IP if joining from console.
		packet.WriteUINT32( pSession->id.m_addr.m_publicIP );
		packet.WriteUINT16( pSession->id.m_addr.m_publicPort );
		packet.WriteConnectionUID(pConnection->uid);
		packet.WriteUINT16((pSession->createFlags & CRYSESSION_CREATE_FLAG_GAME_MASK) >> CRYSESSION_CREATE_FLAG_GAME_FLAGS_SHIFT);
		packet.WriteUINT8(pSession->localConnection.numUsers);
		if (gEnv->IsDedicated())													// Server should flag if its dedicated or not
		{
			packet.WriteBool(true);
		}
		else
		{
			packet.WriteBool(false);
		}
		packet.WriteConnectionUID(pSession->localConnection.uid);
		packet.WriteString(pSession->localConnection.name,CRYLOBBY_USER_NAME_LENGTH);
		packet.WriteData(pSession->localConnection.userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);
		packet.WriteUINT32( pSession->localConnection.userID.m_profileID );

		Send(CryMatchMakingInvalidTaskID, &packet, localSessionIndex, id);

		// Now connect success has been sent to new client it is safe to inform game of the connection
		SessionUserDataEvent(eCLSE_SessionUserJoin, localSessionIndex, id);

		// Send the new clients connection to the old clients
		packet.StartWrite(eGameSpyPT_SessionAddRemoteConnections, true);
		packet.WriteUINT8(1);
		packet.WriteUINT32( pConnection->id.m_publicIP );
		packet.WriteUINT16( pConnection->id.m_publicPort );
		packet.WriteUINT32( pConnection->id.m_privateIP );
		packet.WriteUINT16( pConnection->id.m_privatePort );
		packet.WriteConnectionUID(pConnection->uid);
		packet.WriteUINT8(pConnection->numUsers);
		packet.WriteBool(pConnection->m_isDedicated);
		packet.WriteString(pConnection->name,CRYLOBBY_USER_NAME_LENGTH);
		packet.WriteData(pConnection->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);
		packet.WriteUINT32(pConnection->m_profileID);

		packet.WriteUINT8(0);

		uint32 cookiePos = packet.GetWriteBufferPos();

		for (uint32 i = 0; i < MAX_LOBBY_CONNECTIONS; i++)
		{
			if (pSession->remoteConnection[i].used && (i != id))
			{
				cookies[i] = CreateNATCookie(&pSession->remoteConnection[i].id);

				packet.SetWriteBufferPos(cookiePos);
				packet.WriteUINT32(cookies[i]);

				CryLog("[Lobby] Send new connection %d uid %d to old connection %d uid %d cookie %08x", id, pConnection->uid.m_uid, i, pSession->remoteConnection[i].uid.m_uid, cookies[i]);

				Send(CryMatchMakingInvalidTaskID, &packet, localSessionIndex, i);
			}
		}

		// Send the remote connections to the new client
		uint32 connectionToAdd = 0;

		while (true)
		{
			uint8 numConnections = 0;
			uint8 numAdded = 0;
			uint32 testSize;
			uint32 i;

			packet.StartWrite(eGameSpyPT_SessionAddRemoteConnections, true);

			// Find out how many of the remote connections can fit in the packet

			testSize = packet.GetWriteBufferPos() + CryLobbyPacketUINT8Size;

			for (i = connectionToAdd; i < MAX_LOBBY_CONNECTIONS; i++)
			{
				SSession::SRConnection* pConnectionAdd = &pSession->remoteConnection[i];

				if (pConnectionAdd->used && (pConnectionAdd != pConnection))
				{
					uint32 add =
						CryLobbyPacketUINT32Size +
						CryLobbyPacketUINT16Size +
						CryLobbyPacketUINT32Size +
						CryLobbyPacketUINT16Size +
						CryLobbyPacketConnectionUIDSize +
						CryLobbyPacketUINT8Size +
						CryLobbyPacketBoolSize +
						CRYLOBBY_USER_NAME_LENGTH +
						CRYLOBBY_USER_DATA_SIZE_IN_BYTES +
						CryLobbyPacketUINT32Size +
						CryLobbyPacketUINT8Size +
						CryLobbyPacketUINT32Size;

					if (testSize + add < MAX_LOBBY_PACKET_SIZE)
					{
						testSize += add;
						numConnections++;
					}
					else
					{
						break;
					}
				}
			}

			if (numConnections > 0)
			{
				// Add and send the connections
				packet.WriteUINT8(numConnections);

				for (i = connectionToAdd, numAdded = 0; (i < MAX_LOBBY_CONNECTIONS) && (numAdded < numConnections); i++, connectionToAdd++)
				{
					SSession::SRConnection* pConnectionAdd = &pSession->remoteConnection[i];

					if (pConnectionAdd->used && (pConnectionAdd != pConnection))
					{
						packet.WriteUINT32(pConnectionAdd->id.m_publicIP);
						packet.WriteUINT16(pConnectionAdd->id.m_publicPort);
						packet.WriteUINT32(pConnectionAdd->id.m_privateIP);
						packet.WriteUINT16(pConnectionAdd->id.m_privatePort);
						packet.WriteConnectionUID(pConnectionAdd->uid);
						packet.WriteUINT8(pConnectionAdd->numUsers);
						packet.WriteBool(pConnectionAdd->m_isDedicated);
						packet.WriteString(pConnectionAdd->name,CRYLOBBY_USER_NAME_LENGTH);
						packet.WriteData(pConnectionAdd->userData, CRYLOBBY_USER_DATA_SIZE_IN_BYTES);
						packet.WriteUINT32(pConnectionAdd->m_profileID);

						packet.WriteUINT8(1);
						packet.WriteUINT32(cookies[i]);

						numAdded++;

						CryLog("[Lobby] Send connection %d uid %d to new connection %d uid %d cookie %08x", i, pConnectionAdd->uid.m_uid, id, pConnection->uid.m_uid, cookies[i]);
					}
				}

				Send(CryMatchMakingInvalidTaskID, &packet, localSessionIndex, id);
			}
			else
			{
				// No more connections to send
				break;
			}
		}

		packet.FreeWriteBuffer();
	}
	else
	{
		FreeRemoteConnection(localSessionIndex, id);
		error = eCLE_ConnectionFailed;
	}
	return error;
}

void CCryGameSpyMatchMaking::SessionJoinFail( uint64 sessionID, ECryLobbyError error, CryMatchMakingTaskID returnTaskID, const TNetAddress& addr )
{
	// Can't add to session so send back error
	const uint32 MaxBufferSize = CryLobbyPacketHeaderSize + CryLobbyPacketErrorSize + CryLobbyPacketUINT8Size + CryLobbyPacketUINT32Size + CryLobbyPacketUINT16Size;
	CCrySharedLobbyPacket packet;

	if (packet.CreateWriteBuffer(MaxBufferSize))
	{
		packet.StartWrite(eGameSpyPT_SessionRequestJoinResult, true);
		packet.SetFromSessionSID(sessionID);
		packet.WriteError(error);
		packet.WriteUINT8(returnTaskID);
		packet.WriteUINT32(0);
		packet.WriteUINT16(0);

		Send(CryMatchMakingInvalidTaskID, &packet, CrySessionInvalidHandle, addr);

		packet.FreeWriteBuffer();
	}
}

ECryLobbyError CCryGameSpyMatchMaking::TranslateCDKeyError( char* pErrMsg )
{
	ECryLobbyError		error;

	if ( !strcmp( pErrMsg, "Bad Response" ) )
	{
		error = eCLE_CDKeyMalformed;
	}
	else
	{
		if ( !strcmp( pErrMsg, "Invalid CD Key" ) )
		{
			error = eCLE_CDKeyUnknown;
		}
		else
		{
			if ( !strcmp( pErrMsg, "Invalid authentication" ) )
			{
				error = eCLE_CDKeyAuthFailed;
			}
			else
			{
				if ( !strcmp( pErrMsg, "Your CD Key is disabled. Contact customer service." ) )
				{
					error = eCLE_CDKeyDisabled;
				}
				else
				{
					if ( !strcmp( pErrMsg, "CD Key in use" ) )
					{
						error = eCLE_CDKeyInUse;
					}
					else
					{
						error = eCLE_InternalError;
					}
				}
			}
		}
	}

	return error;
}

#endif // USE_CRYLOBBY_GAMESPY
